pdf-core 0.8.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pdf/core/page.rb CHANGED
@@ -1,44 +1,104 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # prawn/core/page.rb : Implements low-level representation of a PDF page
4
- #
5
- # Copyright February 2010, Gregory Brown. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
- #
9
-
10
3
  require_relative 'graphics_state'
11
4
 
12
5
  module PDF
13
6
  module Core
14
- class Page #:nodoc:
15
- attr_accessor :art_indents, :bleeds, :crops, :document, :margins, :stack,
16
- :trims
17
- attr_writer :content, :dictionary
7
+ # Low-level representation of a PDF page
8
+ #
9
+ # @api private
10
+ class Page
11
+ # Page art box indents relative to page edges.
12
+ #
13
+ # @return [Hash<[:left, :right, :top, :bottom], Numeric>
14
+ attr_accessor :art_indents
15
+
16
+ # Page bleed box indents.
17
+ #
18
+ # @return [Hash<[:left, :right, :top, :bottom], Numeric>
19
+ attr_accessor :bleeds
20
+
21
+ # Page crop box indents.
22
+ #
23
+ # @return [Hash<[:left, :right, :top, :bottom], Numeric>
24
+ attr_accessor :crops
25
+
26
+ # Page trim box indents.
27
+ #
28
+ # @return [Hash<[:left, :right, :top, :bottom], Numeric>
29
+ attr_accessor :trims
30
+
31
+ # Page margins.
32
+ #
33
+ # @return [Hash<[:left, :right, :top, :bottom], Numeric>
34
+ attr_accessor :margins
35
+
36
+ # Owning document.
37
+ #
38
+ # @return [Prawn::Document]
39
+ attr_accessor :document
40
+
41
+ # Graphic state stack.
42
+ #
43
+ # @return [GraphicStateStack]
44
+ attr_accessor :stack
45
+
46
+ # Page content stream reference.
47
+ #
48
+ # @return [PDF::Core::Reference<Hash>]
49
+ attr_writer :content
50
+
51
+ # Page dictionary reference.
52
+ #
53
+ # @return [PDF::Core::Reference<Hash>]
54
+ attr_writer :dictionary
18
55
 
56
+ # A convenince constant of no indents.
19
57
  ZERO_INDENTS = {
20
58
  left: 0,
21
59
  bottom: 0,
22
60
  right: 0,
23
- top: 0
61
+ top: 0,
24
62
  }.freeze
25
63
 
64
+ # @param document [Prawn::Document]
65
+ # @param options [Hash]
66
+ # @option options :margins [Hash{:left, :right, :top, :bottom => Number}, nil]
67
+ # ({ left: 0, right: 0, top: 0, bottom: 0 }) Page margins
68
+ # @option options :crop [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
69
+ # Page crop box
70
+ # @option options :bleed [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
71
+ # Page bleed box
72
+ # @option options :trims [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
73
+ # Page trim box
74
+ # @option options :art_indents [Hash{:left, :right, :top, :bottom => Number}, Numeric>, nil] (ZERO_INDENTS)
75
+ # Page art box indents.
76
+ # @option options :graphic_state [PDF::Core::GraphicState, nil] (nil)
77
+ # Initial graphic state
78
+ # @option options :size [String, Array<Numeric>, nil] ('LETTER')
79
+ # Page size. A string identifies a named page size defined in
80
+ # {PageGeometry}. An array must be a two element array specifying width
81
+ # and height in points.
82
+ # @option options :layout [:portrait, :landscape, nil] (:portrait)
83
+ # Page orientation.
26
84
  def initialize(document, options = {})
27
85
  @document = document
28
- @margins = options[:margins] || { left: 36,
29
- right: 36,
30
- top: 36,
31
- bottom: 36 }
86
+ @margins = options[:margins] || {
87
+ left: 36,
88
+ right: 36,
89
+ top: 36,
90
+ bottom: 36,
91
+ }
32
92
  @crops = options[:crops] || ZERO_INDENTS
33
93
  @bleeds = options[:bleeds] || ZERO_INDENTS
34
94
  @trims = options[:trims] || ZERO_INDENTS
35
95
  @art_indents = options[:art_indents] || ZERO_INDENTS
36
96
  @stack = GraphicStateStack.new(options[:graphic_state])
37
- @size = options[:size] || 'LETTER'
38
- @layout = options[:layout] || :portrait
97
+ @size = options[:size] || 'LETTER'
98
+ @layout = options[:layout] || :portrait
39
99
 
40
- @stamp_stream = nil
41
- @stamp_dictionary = nil
100
+ @stamp_stream = nil
101
+ @stamp_dictionary = nil
42
102
 
43
103
  @content = document.ref({})
44
104
  content << 'q' << "\n"
@@ -50,16 +110,23 @@ module PDF
50
110
  BleedBox: bleed_box,
51
111
  TrimBox: trim_box,
52
112
  ArtBox: art_box,
53
- Contents: content
113
+ Contents: content,
54
114
  )
55
115
 
56
116
  resources[:ProcSet] = %i[PDF Text ImageB ImageC ImageI]
57
117
  end
58
118
 
119
+ # Current graphic state.
120
+ #
121
+ # @return [PDF::Core::GraphicState]
59
122
  def graphic_state
60
123
  stack.current_state
61
124
  end
62
125
 
126
+ # Page layout.
127
+ #
128
+ # @return [:portrait] if page is talled than wider
129
+ # @return [:landscape] otherwise
63
130
  def layout
64
131
  return @layout if defined?(@layout) && @layout
65
132
 
@@ -71,40 +138,63 @@ module PDF
71
138
  end
72
139
  end
73
140
 
141
+ # Page size.
142
+ #
143
+ # @return [Array<Numeric>] a two-element array containing width and height
144
+ # of the page.
74
145
  def size
75
- defined?(@size) && @size || dimensions[2, 2]
146
+ (defined?(@size) && @size) || dimensions[2, 2]
76
147
  end
77
148
 
149
+ # Are we drawing to a stamp right now?
150
+ #
151
+ # @return [Boolean]
78
152
  def in_stamp_stream?
79
153
  !@stamp_stream.nil?
80
154
  end
81
155
 
156
+ # Draw to stamp.
157
+ #
158
+ # @param dictionary [PDF::Core::Reference<Hash>] stamp dictionary
159
+ # @yield outputs to the stamp
160
+ # @return [void]
82
161
  def stamp_stream(dictionary)
83
162
  @stamp_dictionary = dictionary
84
- @stamp_stream = @stamp_dictionary.stream
163
+ @stamp_stream = @stamp_dictionary.stream
85
164
  graphic_stack_size = stack.stack.size
86
165
 
87
166
  document.save_graphics_state
88
- document.send(:freeze_stamp_graphics)
167
+ document.__send__(:freeze_stamp_graphics)
89
168
  yield if block_given?
90
169
 
91
170
  until graphic_stack_size == stack.stack.size
92
171
  document.restore_graphics_state
93
172
  end
94
173
 
95
- @stamp_stream = nil
96
- @stamp_dictionary = nil
174
+ @stamp_stream = nil
175
+ @stamp_dictionary = nil
97
176
  end
98
177
 
178
+ # Current content stream. Can be either the page content stream or a stamp
179
+ # content stream.
180
+ #
181
+ # @return [PDF::Core::Reference<Hash>]
99
182
  def content
100
183
  @stamp_stream || document.state.store[@content]
101
184
  end
102
185
 
186
+ # Current content dictionary. Can be either the page dictionary or a stamp
187
+ # dictionary.
188
+ #
189
+ # @return [PDF::Core::Reference<Hash>]
103
190
  def dictionary
104
- defined?(@stamp_dictionary) && @stamp_dictionary ||
191
+ (defined?(@stamp_dictionary) && @stamp_dictionary) ||
105
192
  document.state.store[@dictionary]
106
193
  end
107
194
 
195
+ # Page resources dictionary.
196
+ #
197
+ # @return [Hash]
108
198
  def resources
109
199
  if dictionary.data[:Resources]
110
200
  document.deref(dictionary.data[:Resources])
@@ -113,6 +203,9 @@ module PDF
113
203
  end
114
204
  end
115
205
 
206
+ # Fonts dictionary.
207
+ #
208
+ # @return [Hash]
116
209
  def fonts
117
210
  if resources[:Font]
118
211
  document.deref(resources[:Font])
@@ -121,6 +214,9 @@ module PDF
121
214
  end
122
215
  end
123
216
 
217
+ # External objects dictionary.
218
+ #
219
+ # @return [Hash]
124
220
  def xobjects
125
221
  if resources[:XObject]
126
222
  document.deref(resources[:XObject])
@@ -129,6 +225,9 @@ module PDF
129
225
  end
130
226
  end
131
227
 
228
+ # Graphic state parameter dictionary.
229
+ #
230
+ # @return [Hash]
132
231
  def ext_gstates
133
232
  if resources[:ExtGState]
134
233
  document.deref(resources[:ExtGState])
@@ -137,6 +236,9 @@ module PDF
137
236
  end
138
237
  end
139
238
 
239
+ # Finalize page.
240
+ #
241
+ # @return [void]
140
242
  def finalize
141
243
  if dictionary.data[:Contents].is_a?(Array)
142
244
  dictionary.data[:Contents].each do |stream|
@@ -147,9 +249,12 @@ module PDF
147
249
  end
148
250
  end
149
251
 
252
+ # Page dimensions.
253
+ #
254
+ # @return [Array<Numeric>]
150
255
  def dimensions
151
256
  coords = PDF::Core::PageGeometry::SIZES[size] || size
152
- [0, 0] +
257
+ coords =
153
258
  case layout
154
259
  when :portrait
155
260
  coords
@@ -159,45 +264,66 @@ module PDF
159
264
  raise PDF::Core::Errors::InvalidPageLayout,
160
265
  'Layout must be either :portrait or :landscape'
161
266
  end
267
+ [0, 0].concat(coords)
162
268
  end
163
269
 
270
+ # A rectangle, expressed in default user space units, defining the extent
271
+ # of the page's meaningful content (including potential white space) as
272
+ # intended by the page's creator.
273
+ #
274
+ # @return [Array<Numeric>]
164
275
  def art_box
165
276
  left, bottom, right, top = dimensions
166
277
  [
167
278
  left + art_indents[:left],
168
279
  bottom + art_indents[:bottom],
169
280
  right - art_indents[:right],
170
- top - art_indents[:top]
281
+ top - art_indents[:top],
171
282
  ]
172
283
  end
173
284
 
285
+ # Page bleed box. A rectangle, expressed in default user space units,
286
+ # defining the region to which the contents of the page should be clipped
287
+ # when output in a production environment.
288
+ #
289
+ # @return [Array<Numeric>]
174
290
  def bleed_box
175
291
  left, bottom, right, top = dimensions
176
292
  [
177
293
  left + bleeds[:left],
178
294
  bottom + bleeds[:bottom],
179
295
  right - bleeds[:right],
180
- top - bleeds[:top]
296
+ top - bleeds[:top],
181
297
  ]
182
298
  end
183
299
 
300
+ # A rectangle, expressed in default user space units, defining the visible
301
+ # region of default user space. When the page is displayed or printed, its
302
+ # contents are to be clipped (cropped) to this rectangle and then imposed
303
+ # on the output medium in some implementation-defined manner.
304
+ #
305
+ # @return [Array<Numeric>]
184
306
  def crop_box
185
307
  left, bottom, right, top = dimensions
186
308
  [
187
309
  left + crops[:left],
188
310
  bottom + crops[:bottom],
189
311
  right - crops[:right],
190
- top - crops[:top]
312
+ top - crops[:top],
191
313
  ]
192
314
  end
193
315
 
316
+ # A rectangle, expressed in default user space units, defining the
317
+ # intended dimensions of the finished page after trimming.
318
+ #
319
+ # @return [Array<Numeric>]
194
320
  def trim_box
195
321
  left, bottom, right, top = dimensions
196
322
  [
197
323
  left + trims[:left],
198
324
  bottom + trims[:bottom],
199
325
  right - trims[:right],
200
- top - trims[:top]
326
+ top - trims[:top],
201
327
  ]
202
328
  end
203
329
 
@@ -9,64 +9,10 @@
9
9
  module PDF
10
10
  module Core
11
11
  # Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
12
- #
13
- # All of these dimensions are in PDF Points (1/72 inch)
14
- #
15
- # ===Inbuilt Sizes:
16
- #
17
- #
18
- # 4A0:: => 4767.87 x 6740.79
19
- # 2A0:: => 3370.39 x 4767.87
20
- # A0:: => 2383.94 x 3370.39
21
- # A1:: => 1683.78 x 2383.94
22
- # A2:: => 1190.55 x 1683.78
23
- # A3:: => 841.89 x 1190.55
24
- # A4:: => 595.28 x 841.89
25
- # A5:: => 419.53 x 595.28
26
- # A6:: => 297.64 x 419.53
27
- # A7:: => 209.76 x 297.64
28
- # A8:: => 147.40 x 209.76
29
- # A9:: => 104.88 x 147.40
30
- # A10:: => 73.70 x 104.88
31
- # B0:: => 2834.65 x 4008.19
32
- # B1:: => 2004.09 x 2834.65
33
- # B2:: => 1417.32 x 2004.09
34
- # B3:: => 1000.63 x 1417.32
35
- # B4:: => 708.66 x 1000.63
36
- # B5:: => 498.90 x 708.66
37
- # B6:: => 354.33 x 498.90
38
- # B7:: => 249.45 x 354.33
39
- # B8:: => 175.75 x 249.45
40
- # B9:: => 124.72 x 175.75
41
- # B10:: => 87.87 x 124.72
42
- # C0:: => 2599.37 x 3676.54
43
- # C1:: => 1836.85 x 2599.37
44
- # C2:: => 1298.27 x 1836.85
45
- # C3:: => 918.43 x 1298.27
46
- # C4:: => 649.13 x 918.43
47
- # C5:: => 459.21 x 649.13
48
- # C6:: => 323.15 x 459.21
49
- # C7:: => 229.61 x 323.15
50
- # C8:: => 161.57 x 229.61
51
- # C9:: => 113.39 x 161.57
52
- # C10:: => 79.37 x 113.39
53
- # RA0:: => 2437.80 x 3458.27
54
- # RA1:: => 1729.13 x 2437.80
55
- # RA2:: => 1218.90 x 1729.13
56
- # RA3:: => 864.57 x 1218.90
57
- # RA4:: => 609.45 x 864.57
58
- # SRA0:: => 2551.18 x 3628.35
59
- # SRA1:: => 1814.17 x 2551.18
60
- # SRA2:: => 1275.59 x 1814.17
61
- # SRA3:: => 907.09 x 1275.59
62
- # SRA4:: => 637.80 x 907.09
63
- # EXECUTIVE:: => 521.86 x 756.00
64
- # FOLIO:: => 612.00 x 936.00
65
- # LEGAL:: => 612.00 x 1008.00
66
- # LETTER:: => 612.00 x 792.00
67
- # TABLOID:: => 792.00 x 1224.00
68
- #
69
12
  module PageGeometry
13
+ # Named page sizes.
14
+ #
15
+ # All of these dimensions are in PDF Points (1/72 inch).
70
16
  SIZES = {
71
17
  '4A0' => [4767.87, 6740.79],
72
18
  '2A0' => [3370.39, 4767.87],
@@ -117,7 +63,7 @@ module PDF
117
63
  'FOLIO' => [612.00, 936.00],
118
64
  'LEGAL' => [612.00, 1008.00],
119
65
  'LETTER' => [612.00, 792.00],
120
- 'TABLOID' => [792.00, 1224.00]
66
+ 'TABLOID' => [792.00, 1224.00],
121
67
  }.freeze
122
68
  end
123
69
  end
@@ -1,43 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # pdf_object.rb : Handles Ruby to PDF object serialization
4
- #
5
- # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
-
9
- # Top level Module
10
- #
11
3
  module PDF
12
4
  module Core
13
5
  module_function
14
6
 
7
+ # Serializes floating number into a string
8
+ #
9
+ # @param num [Numeric]
10
+ # @return [String]
15
11
  def real(num)
16
- num.to_f.round(4)
12
+ result = format('%.5f', num)
13
+ result.sub!(/((?<!\.)0)+\z/, '')
14
+ result
17
15
  end
18
16
 
17
+ # Serializes a n array of numbers. This is specifically for use in PDF
18
+ # content streams.
19
+ #
20
+ # @param array [Array<Numeric>]
21
+ # @return [String]
19
22
  def real_params(array)
20
23
  array.map { |e| real(e) }.join(' ')
21
24
  end
22
25
 
26
+ # Converts string to UTF-16BE encoding as expected by PDF.
27
+ #
28
+ # @param str [String]
29
+ # @return [String]
30
+ # @api private
23
31
  def utf8_to_utf16(str)
24
- (+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE) +
32
+ (+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE) <<
25
33
  str.encode(::Encoding::UTF_16BE)
26
34
  end
27
35
 
28
- # encodes any string into a hex representation. The result is a string
36
+ # Encodes any string into a hex representation. The result is a string
29
37
  # with only 0-9 and a-f characters. That result is valid ASCII so tag
30
- # it as such to account for behaviour of different ruby VMs
38
+ # it as such to account for behaviour of different ruby VMs.
39
+ #
40
+ # @param str [String]
41
+ # @return [String]
31
42
  def string_to_hex(str)
32
- str.unpack('H*').first.force_encoding(::Encoding::US_ASCII)
43
+ str.unpack1('H*').force_encoding(::Encoding::US_ASCII)
33
44
  end
34
45
 
46
+ # Characters to escape in name objects
47
+ # @api private
48
+ ESCAPED_NAME_CHARACTERS = (1..32).to_a + [35, 40, 41, 47, 60, 62] + (127..255).to_a
49
+
50
+ # How to escape special characters in literal strings
51
+ # @api private
52
+ STRING_ESCAPE_MAP = { '(' => '\(', ')' => '\)', '\\' => '\\\\', "\r" => '\r' }.freeze
53
+
35
54
  # Serializes Ruby objects to their PDF equivalents. Most primitive objects
36
55
  # will work as expected, but please note that Name objects are represented
37
56
  # by Ruby Symbol objects and Dictionary objects are represented by Ruby
38
57
  # hashes (keyed by symbols)
39
58
  #
40
- # Examples:
59
+ # Examples:
41
60
  #
42
61
  # pdf_object(true) #=> "true"
43
62
  # pdf_object(false) #=> "false"
@@ -46,45 +65,53 @@ module PDF
46
65
  # pdf_object(:Symbol) #=> "/Symbol"
47
66
  # pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
48
67
  #
68
+ # @param obj [nil, Boolean, Numeric, Array, Hash, Time, Symbol, String,
69
+ # PDF::Core::ByteString, PDF::Core::LiteralString,
70
+ # PDF::Core::NameTree::Node, PDF::Core::NameTree::Value,
71
+ # PDF::Core::OutlineRoot, PDF::Core::OutlineItem, PDF::Core::Reference]
72
+ # Object to serialise
73
+ # @param in_content_stream [Boolean] Specifies whther to use content stream
74
+ # format or object format
75
+ # @return [String]
76
+ # @raise [PDF::Core::Errors::FailedObjectConversion]
49
77
  def pdf_object(obj, in_content_stream = false)
50
78
  case obj
51
- when NilClass then 'null'
52
- when TrueClass then 'true'
79
+ when NilClass then 'null'
80
+ when TrueClass then 'true'
53
81
  when FalseClass then 'false'
54
82
  when Numeric
55
- obj = real(obj) unless obj.is_a?(Integer)
56
-
57
- # NOTE: this can fail on huge floating point numbers, but it seems
58
- # unlikely to ever happen in practice.
59
- num_string = String(obj)
83
+ num_string = obj.is_a?(Integer) ? String(obj) : real(obj)
60
84
 
61
85
  # Truncate trailing fraction zeroes
62
- num_string.sub(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
86
+ num_string.sub!(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
87
+ num_string
63
88
  when Array
64
- '[' + obj.map { |e| pdf_object(e, in_content_stream) }.join(' ') + ']'
89
+ "[#{obj.map { |e| pdf_object(e, in_content_stream) }.join(' ')}]"
65
90
  when PDF::Core::LiteralString
66
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/) { |m| "\\#{m}" }
91
+ obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
67
92
  "(#{obj})"
68
93
  when Time
69
- obj = obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop + "'00'"
70
- obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/) { |m| "\\#{m}" }
94
+ obj = "#{obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop}'00'"
95
+ obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
71
96
  "(#{obj})"
72
97
  when PDF::Core::ByteString
73
- "<#{obj.unpack('H*').first}>"
98
+ "<#{obj.unpack1('H*')}>"
74
99
  when String
75
100
  obj = utf8_to_utf16(obj) unless in_content_stream
76
101
  "<#{string_to_hex(obj)}>"
77
102
  when Symbol
78
- '/' + obj.to_s.unpack('C*').map do |n|
79
- if n < 33 || n > 126 || [35, 40, 41, 47, 60, 62].include?(n)
80
- '#' + n.to_s(16).upcase
103
+ (@symbol_str_cache ||= {})[obj] ||= (+'/') << obj.to_s.unpack('C*').map { |n|
104
+ if ESCAPED_NAME_CHARACTERS.include?(n)
105
+ "##{n.to_s(16).upcase}"
81
106
  else
82
- [n].pack('C*')
107
+ n.chr
83
108
  end
84
- end.join
109
+ }.join
85
110
  when ::Hash
86
111
  output = +'<< '
87
- obj.each do |k, v|
112
+ obj
113
+ .sort_by { |k, _v| k.to_s }
114
+ .each do |(k, v)|
88
115
  unless k.is_a?(String) || k.is_a?(Symbol)
89
116
  raise PDF::Core::Errors::FailedObjectConversion,
90
117
  'A PDF Dictionary must be keyed by names'
@@ -95,12 +122,10 @@ module PDF
95
122
  output << '>>'
96
123
  when PDF::Core::Reference
97
124
  obj.to_s
98
- when PDF::Core::NameTree::Node
125
+ when PDF::Core::NameTree::Node, PDF::Core::OutlineRoot, PDF::Core::OutlineItem
99
126
  pdf_object(obj.to_hash)
100
127
  when PDF::Core::NameTree::Value
101
- pdf_object(obj.name) + ' ' + pdf_object(obj.value)
102
- when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
103
- pdf_object(obj.to_hash)
128
+ "#{pdf_object(obj.name)} #{pdf_object(obj.value)}"
104
129
  else
105
130
  raise PDF::Core::Errors::FailedObjectConversion,
106
131
  "This object cannot be serialized to PDF (#{obj.inspect})"
@@ -1,25 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # reference.rb : Implementation of PDF indirect objects
4
- #
5
- # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
-
9
3
  require 'pdf/core/utils'
10
4
 
11
5
  module PDF
12
6
  module Core
13
- class Reference #:nodoc:
14
- attr_accessor :gen, :data, :offset, :stream, :identifier
7
+ # PDF indirect objects
8
+ #
9
+ # @api private
10
+ class Reference
11
+ # Object identifier
12
+ # @return [Integer]
13
+ attr_accessor :identifier
14
+
15
+ # Object generation
16
+ # @return [Integer]
17
+ attr_accessor :gen
18
+
19
+ # Object data
20
+ # @return [any]
21
+ attr_accessor :data
22
+
23
+ # Offset of the serialized object in the document
24
+ # @return [Integer]
25
+ attr_accessor :offset
15
26
 
27
+ # Object stream
28
+ # @return [Stream]
29
+ attr_accessor :stream
30
+
31
+ # In PDF only dict object can have a stream attached. This exception
32
+ # indicates someone tried to add a stream to another kind of object.
33
+ class CannotAttachStream < StandardError
34
+ # @param message [String] Error message
35
+ def initialize(message = 'Cannot attach stream to a non-dictionary object')
36
+ super
37
+ end
38
+ end
39
+
40
+ # @param id [Integer] Object identifier
41
+ # @param data [any] Object data
16
42
  def initialize(id, data)
17
43
  @identifier = id
18
- @gen = 0
19
- @data = data
20
- @stream = Stream.new
44
+ @gen = 0
45
+ @data = data
46
+ @stream = Stream.new
21
47
  end
22
48
 
49
+ # Serialized PDF object
50
+ #
51
+ # @return [String]
23
52
  def object
24
53
  output = +"#{@identifier} #{gen} obj\n"
25
54
  if @stream.empty?
@@ -32,20 +61,31 @@ module PDF
32
61
  output << "endobj\n"
33
62
  end
34
63
 
64
+ # Appends data to object stream
65
+ #
66
+ # @param io [String] data
67
+ # @return [io]
68
+ # @raise [CannotAttachStream] if object is not a dict
35
69
  def <<(io)
36
70
  unless @data.is_a?(::Hash)
37
- raise 'Cannot attach stream to non-dictionary object'
71
+ raise CannotAttachStream
38
72
  end
73
+
39
74
  (@stream ||= Stream.new) << io
40
75
  end
41
76
 
77
+ # Object reference in PDF format
78
+ #
79
+ # @return [String]
42
80
  def to_s
43
81
  "#{@identifier} #{gen} R"
44
82
  end
45
83
 
46
- # Creates a deep copy of this ref. If +share+ is provided, shares the
47
- # given dictionary entries between the old ref and the new.
84
+ # Creates a deep copy of this ref.
48
85
  #
86
+ # @param share [Array<Symbol>] a list of dictionary entries to share
87
+ # between the old ref and the new
88
+ # @return [Reference]
49
89
  def deep_copy(share = [])
50
90
  r = dup
51
91
 
@@ -66,8 +106,11 @@ module PDF
66
106
  end
67
107
 
68
108
  # Replaces the data and stream with that of other_ref.
109
+ #
110
+ # @param other_ref [Reference]
111
+ # @return [void]
69
112
  def replace(other_ref)
70
- @data = other_ref.data
113
+ @data = other_ref.data
71
114
  @stream = other_ref.stream
72
115
  end
73
116
  end