pdf-core 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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