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.
@@ -4,7 +4,9 @@ require 'stringio'
4
4
 
5
5
  module PDF
6
6
  module Core
7
+ # Document renderer serializes document into its binary representation.
7
8
  class Renderer
9
+ # @param state [PDF::Core::DocumentState]
8
10
  def initialize(state)
9
11
  @state = state
10
12
  @state.populate_pages_from_store(self)
@@ -14,92 +16,109 @@ module PDF
14
16
  @page_number = 0
15
17
  end
16
18
 
19
+ # Document state
20
+ # @return [PDF::Core::DocumentState]
17
21
  attr_reader :state
18
22
 
19
- # Creates a new Reference and adds it to the Document's object list. The
20
- # +data+ argument is anything that Prawn.pdf_object() can convert.
21
- #
22
- # Returns the identifier which points to the reference in the ObjectStore
23
+ # Creates a new Reference and adds it to the Document's object list.
23
24
  #
25
+ # @param data [any] anything that {PDF::Core.pdf_object} can convert.
26
+ # @return [Integer] the identifier of the reference
24
27
  def ref(data)
25
28
  ref!(data).identifier
26
29
  end
27
30
 
28
- # Like ref, but returns the actual reference instead of its identifier.
31
+ # Like {ref}, but returns the actual reference instead of its identifier.
29
32
  #
30
33
  # While you can use this to build up nested references within the object
31
34
  # tree, it is recommended to persist only identifiers, and then provide
32
- # helper methods to look up the actual references in the ObjectStore
33
- # if needed. If you take this approach, Document::Snapshot
34
- # will probably work with your extension
35
+ # helper methods to look up the actual references in the {ObjectStore} if
36
+ # needed. If you take this approach, `Document::Snapshot` will probably
37
+ # work with your extension.
35
38
  #
39
+ # @param data [any] anything that {PDF::Core.pdf_object} can convert.
40
+ # @return [PDF::Core::Reference]
36
41
  def ref!(data)
37
42
  state.store.ref(data)
38
43
  end
39
44
 
40
45
  # At any stage in the object tree an object can be replaced with an
41
46
  # indirect reference. To get access to the object safely, regardless
42
- # of if it's hidden behind a Prawn::Reference, wrap it in deref().
47
+ # of if it's hidden behind a {Reference}, wrap it in `deref()`.
43
48
  #
49
+ # @param obj [PDF::Core::Reference, any]
50
+ # @return [any]
44
51
  def deref(obj)
45
52
  obj.is_a?(PDF::Core::Reference) ? obj.data : obj
46
53
  end
47
54
 
48
55
  # Appends a raw string to the current page content.
49
56
  #
50
- # # Raw line drawing example:
51
- # x1,y1,x2,y2 = 100,500,300,550
57
+ # @example Raw line drawing example
58
+ # x1, y1, x2, y2 = 100, 500, 300, 550
52
59
  #
53
- # pdf.add_content("#{PDF::Core.real_params([x1, y1])} m") # move
54
- # pdf.add_content("#{PDF::Core.real_params([ x2, y2 ])} l") # draw path
55
- # pdf.add_content('S') # stroke
60
+ # pdf.add_content("#{PDF::Core.real_params([x1, y1])} m") # move
61
+ # pdf.add_content("#{PDF::Core.real_params([ x2, y2 ])} l") # draw path
62
+ # pdf.add_content('S') # stroke
56
63
  #
64
+ # @param str [String]
65
+ # @return [void]
57
66
  def add_content(str)
58
67
  save_graphics_state if graphic_state.nil?
59
68
  state.page.content << str << "\n"
60
69
  end
61
70
 
62
- # The Name dictionary (PDF spec 3.6.3) for this document. It is
63
- # lazily initialized, so that documents that do not need a name
64
- # dictionary do not incur the additional overhead.
71
+ # The Name dictionary for this document. It is lazily initialized, so that
72
+ # documents that do not need a name dictionary do not incur the additional
73
+ # overhead.
65
74
  #
75
+ # @return [PDF::Core::Reference<Hash>]
76
+ # @see # PDF 1.7 spec, section 3.6.3 Name Dictionary
66
77
  def names
67
78
  state.store.root.data[:Names] ||= ref!(Type: :Names)
68
79
  end
69
80
 
70
81
  # Returns true if the Names dictionary is in use for this document.
71
82
  #
83
+ # @return [Boolean]
72
84
  def names?
73
- state.store.root.data[:Names]
85
+ state.store.root.data.key?(:Names)
74
86
  end
75
87
 
76
88
  # Defines a block to be called just before the document is rendered.
77
89
  #
90
+ # @yieldparam document_state [PDF::Core::DocumentState]
91
+ # @return [void]
78
92
  def before_render(&block)
79
93
  state.before_render_callbacks << block
80
94
  end
81
95
 
82
96
  # Defines a block to be called just before a new page is started.
83
97
  #
98
+ # @yieldparam document_state [PDF::Core::DocumentState]
99
+ # @return [void]
84
100
  def on_page_create(&block)
85
- state.on_page_create_callback =
86
- if block_given?
87
- block
88
- end
101
+ state.on_page_create_callback = block
89
102
  end
90
103
 
104
+ # Create a new page and set it current.
105
+ #
106
+ # @param options [Hash]
107
+ # @option options :size [String, Array<Numeric>]
108
+ # @option options :layout [:portrait, :landscape]
109
+ # @return [void]
91
110
  def start_new_page(options = {})
92
111
  last_page = state.page
93
112
  if last_page
94
- last_page_size = last_page.size
95
- last_page_layout = last_page.layout
113
+ last_page_size = last_page.size
114
+ last_page_layout = last_page.layout
96
115
  last_page_margins = last_page.margins
97
116
  end
98
117
 
99
118
  page_options = {
100
119
  size: options[:size] || last_page_size,
101
120
  layout: options[:layout] || last_page_layout,
102
- margins: last_page_margins
121
+ margins: last_page_margins,
103
122
  }
104
123
  if last_page
105
124
  if last_page.graphic_state
@@ -122,6 +141,9 @@ module PDF
122
141
  state.on_page_create_action(self)
123
142
  end
124
143
 
144
+ # Number of pages in the document.
145
+ #
146
+ # @return [Integer]
125
147
  def page_count
126
148
  state.page_count
127
149
  end
@@ -129,16 +151,21 @@ module PDF
129
151
  # Re-opens the page with the given (1-based) page number so that you can
130
152
  # draw on it.
131
153
  #
132
- # See Prawn::Document#number_pages for a sample usage of this capability.
133
-
154
+ # @param page_number [Integer]
155
+ # @return [void]
156
+ # @see # Prawn::Document#number_pages for a sample usage of this capability.
134
157
  def go_to_page(page_number)
135
158
  @page_number = page_number
136
159
  state.page = state.pages[page_number - 1]
137
160
  end
138
161
 
162
+ # Finalize all pages
163
+ #
164
+ # @api private
165
+ # @return [void]
139
166
  def finalize_all_page_contents
140
167
  (1..page_count).each do |i|
141
- go_to_page i
168
+ go_to_page(i)
142
169
  while graphic_stack.present?
143
170
  restore_graphics_state
144
171
  end
@@ -146,10 +173,13 @@ module PDF
146
173
  end
147
174
  end
148
175
 
149
- # raise the PDF version of the file we're going to generate.
176
+ # Raise the PDF version of the file we're going to generate.
150
177
  # A private method, designed for internal use when the user adds a feature
151
178
  # to their document that requires a particular version.
152
179
  #
180
+ # @param min [Float]
181
+ # @return [void]
182
+ # @api private
153
183
  def min_version(min)
154
184
  state.version = min if min > state.version
155
185
  end
@@ -157,35 +187,41 @@ module PDF
157
187
  # Renders the PDF document to string.
158
188
  # Pass an open file descriptor to render to file.
159
189
  #
160
- def render(output = StringIO.new)
161
- if output.instance_of?(StringIO)
162
- output.set_encoding(::Encoding::ASCII_8BIT)
163
- end
190
+ # @param output [#<<]
191
+ # @return [String]
192
+ def render(output = nil)
193
+ buffer = StringIO.new.binmode
194
+
164
195
  finalize_all_page_contents
165
196
 
166
- render_header(output)
167
- render_body(output)
168
- render_xref(output)
169
- render_trailer(output)
170
- if output.instance_of?(StringIO)
171
- str = output.string
172
- str.force_encoding(::Encoding::ASCII_8BIT)
173
- return str
174
- else
175
- return nil
197
+ render_header(buffer)
198
+ render_body(buffer)
199
+ render_xref(buffer)
200
+ render_trailer(buffer)
201
+
202
+ if output.respond_to?(:<<)
203
+ output << buffer.string
176
204
  end
205
+
206
+ buffer.string
177
207
  end
178
208
 
179
209
  # Renders the PDF document to file.
180
210
  #
211
+ # @example
181
212
  # pdf.render_file 'foo.pdf'
182
213
  #
214
+ # @param filename [String, #to_path, Integer]
215
+ # @return [void]
183
216
  def render_file(filename)
184
217
  File.open(filename, 'wb') { |f| render(f) }
185
218
  end
186
219
 
187
220
  # Write out the PDF Header, as per spec 3.4.1
188
221
  #
222
+ # @api private
223
+ # @param output [#<<]
224
+ # @return [void]
189
225
  def render_header(output)
190
226
  state.before_render_actions(self)
191
227
 
@@ -198,30 +234,39 @@ module PDF
198
234
 
199
235
  # Write out the PDF Body, as per spec 3.4.2
200
236
  #
237
+ # @api private
238
+ # @param output [(#<<, #size)]
239
+ # @return [void]
201
240
  def render_body(output)
202
241
  state.render_body(output)
203
242
  end
204
243
 
205
244
  # Write out the PDF Cross Reference Table, as per spec 3.4.3
206
245
  #
246
+ # @api private
247
+ # @param output [(#<<, #size)]
248
+ # @return [void]
207
249
  def render_xref(output)
208
250
  @xref_offset = output.size
209
251
  output << "xref\n"
210
252
  output << "0 #{state.store.size + 1}\n"
211
253
  output << "0000000000 65535 f \n"
212
254
  state.store.each do |ref|
213
- output.printf('%010d', ref.offset)
255
+ output.printf('%<offset>010d', offset: ref.offset)
214
256
  output << " 00000 n \n"
215
257
  end
216
258
  end
217
259
 
218
260
  # Write out the PDF Trailer, as per spec 3.4.4
219
261
  #
262
+ # @api private
263
+ # @param output [#<<]
264
+ # @return [void]
220
265
  def render_trailer(output)
221
266
  trailer_hash = {
222
267
  Size: state.store.size + 1,
223
268
  Root: state.store.root,
224
- Info: state.store.info
269
+ Info: state.store.info,
225
270
  }
226
271
  trailer_hash.merge!(state.trailer) if state.trailer
227
272
 
@@ -232,14 +277,29 @@ module PDF
232
277
  output << '%%EOF' << "\n"
233
278
  end
234
279
 
280
+ # Open (save) current graphic state in the content stream.
281
+ #
282
+ # @return [void]
235
283
  def open_graphics_state
236
- add_content 'q'
284
+ add_content('q')
237
285
  end
238
286
 
287
+ # Close current graphic state (restore previous) in the content stream.
288
+ #
289
+ # @return [void]
239
290
  def close_graphics_state
240
- add_content 'Q'
291
+ add_content('Q')
241
292
  end
242
293
 
294
+ # Save surrent graphic state both in the graphic state stack and in the
295
+ # page content stream.
296
+ #
297
+ # If a block is given graphic state is automatically restored after the
298
+ # block execution.
299
+ #
300
+ # @param graphic_state [PDF::Core::GraphicState]
301
+ # @yield
302
+ # @return [void]
243
303
  def save_graphics_state(graphic_state = nil)
244
304
  graphic_stack.save_graphic_state(graphic_state)
245
305
  open_graphics_state
@@ -252,12 +312,15 @@ module PDF
252
312
  # Returns true if content streams will be compressed before rendering,
253
313
  # false otherwise
254
314
  #
315
+ # @return [Boolean]
255
316
  def compression_enabled?
256
317
  state.compress
257
318
  end
258
319
 
259
320
  # Pops the last saved graphics state off the graphics state stack and
260
321
  # restores the state to those values
322
+ #
323
+ # @return [void]
261
324
  def restore_graphics_state
262
325
  if graphic_stack.empty?
263
326
  raise PDF::Core::Errors::EmptyGraphicStateStack,
@@ -267,10 +330,16 @@ module PDF
267
330
  graphic_stack.restore_graphic_state
268
331
  end
269
332
 
333
+ # Graphic state stack of the current document.
334
+ #
335
+ # @return [PDF::Core::GraphicStateStack]
270
336
  def graphic_stack
271
337
  state.page.stack
272
338
  end
273
339
 
340
+ # Current graphic state
341
+ #
342
+ # @return [PDF::Core::GraphicState]
274
343
  def graphic_state
275
344
  save_graphics_state unless graphic_stack.current_state
276
345
  graphic_stack.current_state
@@ -8,36 +8,56 @@
8
8
 
9
9
  module PDF
10
10
  module Core
11
+ # PDF Stream object
11
12
  class Stream
13
+ # Stream filters
14
+ # @return [PDF::Core::FilterList]
12
15
  attr_reader :filters
13
16
 
17
+ # @param io [String] must be mutable
14
18
  def initialize(io = nil)
15
19
  @filtered_stream = ''
16
20
  @stream = io
17
21
  @filters = FilterList.new
18
22
  end
19
23
 
24
+ # Append data to stream.
25
+ #
26
+ # @param io [String]
27
+ # @return [self]
20
28
  def <<(io)
21
29
  (@stream ||= +'') << io
22
30
  @filtered_stream = nil
23
31
  self
24
32
  end
25
33
 
34
+ # Set up stream to be compressed when serialized.
35
+ #
36
+ # @return [void]
26
37
  def compress!
27
- unless @filters.names.include? :FlateDecode
38
+ unless @filters.names.include?(:FlateDecode)
28
39
  @filtered_stream = nil
29
40
  @filters << :FlateDecode
30
41
  end
31
42
  end
32
43
 
44
+ # Is this stream compressed?
45
+ #
46
+ # @return [Boolean]
33
47
  def compressed?
34
- @filters.names.include? :FlateDecode
48
+ @filters.names.include?(:FlateDecode)
35
49
  end
36
50
 
51
+ # Is there any data in this stream?
52
+ #
53
+ # @return [Boolean]
37
54
  def empty?
38
55
  @stream.nil?
39
56
  end
40
57
 
58
+ # Stream data with filters applied.
59
+ #
60
+ # @return [Stream]
41
61
  def filtered_stream
42
62
  if @stream
43
63
  if @filtered_stream.nil?
@@ -46,20 +66,25 @@ module PDF
46
66
  @filters.each do |(filter_name, params)|
47
67
  filter = PDF::Core::Filters.const_get(filter_name)
48
68
  if filter
49
- @filtered_stream = filter.encode @filtered_stream, params
69
+ @filtered_stream = filter.encode(@filtered_stream, params)
50
70
  end
51
71
  end
52
72
  end
53
73
 
54
74
  @filtered_stream
55
- # XXX Fillter stream
56
75
  end
57
76
  end
58
77
 
78
+ # Size of data in the stream
79
+ #
80
+ # @return [Integer]
59
81
  def length
60
82
  @stream.length
61
83
  end
62
84
 
85
+ # Serialized stream data
86
+ #
87
+ # @return [String]
63
88
  def object
64
89
  if filtered_stream
65
90
  "stream\n#{filtered_stream}\nendstream\n"
@@ -68,13 +93,16 @@ module PDF
68
93
  end
69
94
  end
70
95
 
96
+ # Stream dictionary
97
+ #
98
+ # @return [Hash]
71
99
  def data
72
100
  if @stream
73
101
  filter_names = @filters.names
74
102
  filter_params = @filters.decode_params
75
103
 
76
104
  d = {
77
- Length: filtered_stream.length
105
+ Length: filtered_stream.length,
78
106
  }
79
107
  if filter_names.any?
80
108
  d[:Filter] = filter_names
@@ -89,9 +117,17 @@ module PDF
89
117
  end
90
118
  end
91
119
 
120
+ # String representation of the stream for debugging purposes.
121
+ #
122
+ # @return [String]
92
123
  def inspect
93
- "#<#{self.class.name}:0x#{format '%014x', object_id} "\
94
- "@stream=#{@stream.inspect}, @filters=#{@filters.inspect}>"
124
+ format(
125
+ '#<%<class>s:0x%<object_id>014x @stream=%<stream>s, @filters=%<filters>s>',
126
+ class: self.class.name,
127
+ object_id: object_id,
128
+ stream: @stream.inspect,
129
+ filters: @filters.inspect,
130
+ )
95
131
  end
96
132
  end
97
133
  end