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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/lib/pdf/core/annotations.rb +51 -13
- data/lib/pdf/core/byte_string.rb +3 -2
- data/lib/pdf/core/destinations.rb +64 -24
- data/lib/pdf/core/document_state.rb +100 -17
- data/lib/pdf/core/filter_list.rb +44 -5
- data/lib/pdf/core/filters.rb +26 -7
- data/lib/pdf/core/graphics_state.rb +74 -30
- data/lib/pdf/core/literal_string.rb +9 -10
- data/lib/pdf/core/name_tree.rb +76 -16
- data/lib/pdf/core/object_store.rb +69 -19
- data/lib/pdf/core/outline_item.rb +53 -5
- data/lib/pdf/core/outline_root.rb +18 -2
- data/lib/pdf/core/page.rb +158 -32
- data/lib/pdf/core/page_geometry.rb +4 -58
- data/lib/pdf/core/pdf_object.rb +62 -37
- data/lib/pdf/core/reference.rb +58 -15
- data/lib/pdf/core/renderer.rb +116 -47
- data/lib/pdf/core/stream.rb +43 -7
- data/lib/pdf/core/text.rb +255 -106
- data/lib/pdf/core/utils.rb +10 -1
- data/lib/pdf/core.rb +26 -16
- data/pdf-core.gemspec +31 -27
- data.tar.gz.sig +0 -0
- metadata +36 -101
- metadata.gz.sig +2 -1
- data/Gemfile +0 -5
- data/Rakefile +0 -17
data/lib/pdf/core/renderer.rb
CHANGED
@@ -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.
|
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
|
-
#
|
34
|
-
#
|
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
|
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
|
-
#
|
51
|
-
#
|
57
|
+
# @example Raw line drawing example
|
58
|
+
# x1, y1, x2, y2 = 100, 500, 300, 550
|
52
59
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
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
|
63
|
-
#
|
64
|
-
#
|
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
|
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
|
95
|
-
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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(
|
167
|
-
render_body(
|
168
|
-
render_xref(
|
169
|
-
render_trailer(
|
170
|
-
|
171
|
-
|
172
|
-
|
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('
|
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
|
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
|
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
|
data/lib/pdf/core/stream.rb
CHANGED
@@ -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?
|
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?
|
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
|
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
|
-
|
94
|
-
|
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
|