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