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.
@@ -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