prawn-templates 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING +2 -0
- data/GPLv2 +340 -0
- data/GPLv3 +674 -0
- data/LICENSE +56 -0
- data/lib/pdf/core/document_state.rb +26 -18
- data/lib/pdf/core/object_store.rb +8 -3
- data/lib/pdf/core/page.rb +75 -9
- data/lib/prawn/document/internals.rb +1 -1
- data/lib/prawn/templates.rb +71 -40
- data/lib/prawn/text.rb +7 -3
- data/prawn-templates.gemspec +27 -17
- metadata +37 -19
data/LICENSE
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Prawn::Templates is copyrighted free software produced by James Healy
|
2
|
+
along with community contributions.
|
3
|
+
|
4
|
+
Licensing terms follow:
|
5
|
+
|
6
|
+
You can redistribute Prawn::Templates and/or modify it under either the terms of the GPLv2
|
7
|
+
or GPLv3 (see GPLv2 and GPLv3 files), or the conditions below:
|
8
|
+
|
9
|
+
1. You may make and give away verbatim copies of the source form of the
|
10
|
+
software without restriction, provided that you duplicate all of the
|
11
|
+
original copyright notices and associated disclaimers.
|
12
|
+
|
13
|
+
2. You may modify your copy of the software in any way, provided that
|
14
|
+
you do at least ONE of the following:
|
15
|
+
|
16
|
+
a) place your modifications in the Public Domain or otherwise
|
17
|
+
make them Freely Available, such as by posting said
|
18
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
19
|
+
the author to include your modifications in the software.
|
20
|
+
|
21
|
+
b) use the modified software only within your corporation or
|
22
|
+
organization.
|
23
|
+
|
24
|
+
c) rename any non-standard executables so the names do not conflict
|
25
|
+
with standard executables, which must also be provided.
|
26
|
+
|
27
|
+
d) make other distribution arrangements with the author.
|
28
|
+
|
29
|
+
3. You may distribute the software in object code or executable
|
30
|
+
form, provided that you do at least ONE of the following:
|
31
|
+
|
32
|
+
a) distribute the executables and library files of the software,
|
33
|
+
together with instructions (in the manual page or equivalent)
|
34
|
+
on where to get the original distribution.
|
35
|
+
|
36
|
+
b) accompany the distribution with the machine-readable source of
|
37
|
+
the software.
|
38
|
+
|
39
|
+
c) give non-standard executables non-standard names, with
|
40
|
+
instructions on where to get the original software distribution.
|
41
|
+
|
42
|
+
d) make other distribution arrangements with the author.
|
43
|
+
|
44
|
+
4. You may modify and include the part of the software into any other
|
45
|
+
software (possibly commercial).
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
@@ -5,28 +5,36 @@ module PDF
|
|
5
5
|
normalize_metadata(options)
|
6
6
|
|
7
7
|
if options[:template]
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
@store =
|
9
|
+
if options[:print_scaling]
|
10
|
+
PDF::Core::ObjectStore.new(
|
11
|
+
template: options[:template],
|
12
|
+
print_scaling: options[:print_scaling]
|
13
|
+
)
|
14
|
+
else
|
15
|
+
PDF::Core::ObjectStore.new(template: options[:template])
|
16
|
+
end
|
13
17
|
@store.info.data.merge!(options[:info]) if options[:info]
|
14
18
|
else
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
@store =
|
20
|
+
if options[:print_scaling]
|
21
|
+
PDF::Core::ObjectStore.new(
|
22
|
+
info: options[:info],
|
23
|
+
print_scaling: options[:print_scaling]
|
24
|
+
)
|
25
|
+
else
|
26
|
+
PDF::Core::ObjectStore.new(info: options[:info])
|
27
|
+
end
|
20
28
|
end
|
21
29
|
|
22
|
-
@version
|
23
|
-
@pages
|
24
|
-
@page
|
25
|
-
@trailer
|
26
|
-
@compress
|
27
|
-
@encrypt
|
28
|
-
@encryption_key
|
29
|
-
@skip_encoding
|
30
|
+
@version = 1.3
|
31
|
+
@pages = []
|
32
|
+
@page = nil
|
33
|
+
@trailer = options.fetch(:trailer, {})
|
34
|
+
@compress = options.fetch(:compress, false)
|
35
|
+
@encrypt = options.fetch(:encrypt, false)
|
36
|
+
@encryption_key = options[:encryption_key]
|
37
|
+
@skip_encoding = options.fetch(:skip_encoding, false)
|
30
38
|
@before_render_callbacks = []
|
31
39
|
@on_page_create_callback = nil
|
32
40
|
end
|
@@ -8,14 +8,19 @@ module PDF
|
|
8
8
|
load_file(opts[:template]) if opts[:template]
|
9
9
|
|
10
10
|
@info ||= ref(opts[:info] || {}).identifier
|
11
|
-
@root ||= ref(:
|
11
|
+
@root ||= ref(Type: :Catalog).identifier
|
12
12
|
if opts[:print_scaling] == :none
|
13
|
-
root.data[:ViewerPreferences] = { :
|
13
|
+
root.data[:ViewerPreferences] = { PrintScaling: :None }
|
14
14
|
end
|
15
15
|
if pages.nil?
|
16
|
-
root.data[:Pages] = ref(:
|
16
|
+
root.data[:Pages] = ref(Type: :Pages, Count: 0, Kids: [])
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
def utf8?(str)
|
21
|
+
str.force_encoding(::Encoding::UTF_8)
|
22
|
+
str.valid_encoding?
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
data/lib/pdf/core/page.rb
CHANGED
@@ -1,9 +1,29 @@
|
|
1
1
|
module PDF
|
2
2
|
module Core
|
3
3
|
class Page #:nodoc:
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
def initialize(document, options = {})
|
5
|
+
@document = document
|
6
|
+
@margins = options[:margins] || {
|
7
|
+
left: 36,
|
8
|
+
right: 36,
|
9
|
+
top: 36,
|
10
|
+
bottom: 36
|
11
|
+
}
|
12
|
+
@crops = options[:crops] || ZERO_INDENTS
|
13
|
+
@bleeds = options[:bleeds] || ZERO_INDENTS
|
14
|
+
@trims = options[:trims] || ZERO_INDENTS
|
15
|
+
@art_indents = options[:art_indents] || ZERO_INDENTS
|
16
|
+
@stack = GraphicStateStack.new(options[:graphic_state])
|
17
|
+
if options[:object_id]
|
18
|
+
init_from_object(options)
|
19
|
+
else
|
20
|
+
init_new_page(options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# As per the PDF spec, each page can have multiple content streams. This
|
25
|
+
# will add a fresh, empty content stream this the page, mainly for use in
|
26
|
+
# loading template files.
|
7
27
|
#
|
8
28
|
def new_content_stream
|
9
29
|
return if in_stamp_stream?
|
@@ -11,22 +31,68 @@ module PDF
|
|
11
31
|
unless dictionary.data[:Contents].is_a?(Array)
|
12
32
|
dictionary.data[:Contents] = [content]
|
13
33
|
end
|
14
|
-
@content
|
34
|
+
@content = document.ref({})
|
15
35
|
dictionary.data[:Contents] << document.state.store[@content]
|
16
36
|
document.open_graphics_state
|
17
37
|
end
|
18
38
|
|
39
|
+
def imported_page?
|
40
|
+
@imported_page
|
41
|
+
end
|
42
|
+
|
43
|
+
def dimensions
|
44
|
+
return inherited_dictionary_value(:MediaBox) if imported_page?
|
45
|
+
|
46
|
+
coords = PDF::Core::PageGeometry::SIZES[size] || size
|
47
|
+
[0, 0] +
|
48
|
+
case layout
|
49
|
+
when :portrait
|
50
|
+
coords
|
51
|
+
when :landscape
|
52
|
+
coords.reverse
|
53
|
+
else
|
54
|
+
raise PDF::Core::Errors::InvalidPageLayout,
|
55
|
+
'Layout must be either :portrait or :landscape'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
19
59
|
def init_from_object(options)
|
20
60
|
@dictionary = options[:object_id].to_i
|
21
|
-
|
61
|
+
if options[:page_template]
|
62
|
+
dictionary.data[:Parent] = document.state.store.pages
|
63
|
+
end
|
22
64
|
|
23
65
|
unless dictionary.data[:Contents].is_a?(Array) # content only on leafs
|
24
|
-
@content
|
66
|
+
@content = dictionary.data[:Contents].identifier
|
25
67
|
end
|
26
68
|
|
27
|
-
@stamp_stream
|
28
|
-
@stamp_dictionary
|
29
|
-
@imported_page
|
69
|
+
@stamp_stream = nil
|
70
|
+
@stamp_dictionary = nil
|
71
|
+
@imported_page = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def init_new_page(options)
|
75
|
+
@size = options[:size] || 'LETTER'
|
76
|
+
@layout = options[:layout] || :portrait
|
77
|
+
|
78
|
+
@stamp_stream = nil
|
79
|
+
@stamp_dictionary = nil
|
80
|
+
@imported_page = false
|
81
|
+
|
82
|
+
@content = document.ref({})
|
83
|
+
content << 'q' << "\n"
|
84
|
+
@dictionary = document.ref(
|
85
|
+
Type: :Page,
|
86
|
+
Parent: document.state.store.pages,
|
87
|
+
MediaBox: dimensions,
|
88
|
+
CropBox: crop_box,
|
89
|
+
BleedBox: bleed_box,
|
90
|
+
TrimBox: trim_box,
|
91
|
+
ArtBox: art_box,
|
92
|
+
Contents: content
|
93
|
+
)
|
94
|
+
|
95
|
+
resources[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI]
|
30
96
|
end
|
31
97
|
end
|
32
98
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Prawn
|
2
2
|
class Document
|
3
3
|
module Internals
|
4
|
-
delegate [
|
4
|
+
delegate [:open_graphics_state] => :renderer
|
5
5
|
|
6
6
|
# adds a new, empty content stream to each page. Used in templating so
|
7
7
|
# that imported content streams can be left pristine
|
data/lib/prawn/templates.rb
CHANGED
@@ -3,12 +3,12 @@ require 'pdf/reader'
|
|
3
3
|
require 'pdf/core'
|
4
4
|
require 'prawn/text'
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
11
|
-
require_relative
|
6
|
+
require_relative '../pdf/core/document_state'
|
7
|
+
require_relative '../pdf/core/errors'
|
8
|
+
require_relative '../pdf/core/object_store'
|
9
|
+
require_relative '../pdf/core/page'
|
10
|
+
require_relative 'text'
|
11
|
+
require_relative 'document/internals'
|
12
12
|
|
13
13
|
module Prawn
|
14
14
|
# @private
|
@@ -26,24 +26,28 @@ module Prawn
|
|
26
26
|
def start_new_page(options = {})
|
27
27
|
return super unless options[:template]
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
last_page = state.page
|
30
|
+
if last_page
|
31
|
+
last_page_size = last_page.size
|
32
|
+
last_page_layout = last_page.layout
|
32
33
|
last_page_margins = last_page.margins.dup
|
33
34
|
end
|
34
35
|
|
35
36
|
page_options = {
|
36
|
-
:
|
37
|
-
:
|
38
|
-
:
|
37
|
+
size: options[:size] || last_page_size,
|
38
|
+
layout: options[:layout] || last_page_layout,
|
39
|
+
margins: last_page_margins
|
39
40
|
}
|
40
41
|
if last_page
|
41
|
-
|
42
|
+
if last_page.graphic_state
|
43
|
+
new_graphic_state = last_page.graphic_state.dup
|
44
|
+
end
|
42
45
|
|
43
|
-
# erase the color space so that it gets reset on new page for fussy
|
46
|
+
# erase the color space so that it gets reset on new page for fussy
|
47
|
+
# pdf-readers
|
44
48
|
new_graphic_state.color_space = {} if new_graphic_state
|
45
49
|
|
46
|
-
page_options
|
50
|
+
page_options[:graphic_state] = new_graphic_state
|
47
51
|
end
|
48
52
|
|
49
53
|
merge_template_options(page_options, options)
|
@@ -67,7 +71,11 @@ module Prawn
|
|
67
71
|
state.insert_page(state.page, @page_number)
|
68
72
|
@page_number += 1
|
69
73
|
|
70
|
-
|
74
|
+
if @background
|
75
|
+
canvas do
|
76
|
+
image(@background, scale: @background_scale, at: bounds.top_left)
|
77
|
+
end
|
78
|
+
end
|
71
79
|
@y = @bounding_box.absolute_top
|
72
80
|
|
73
81
|
float do
|
@@ -77,8 +85,11 @@ module Prawn
|
|
77
85
|
end
|
78
86
|
|
79
87
|
def merge_template_options(page_options, options)
|
80
|
-
object_id = state.store.import_page(
|
81
|
-
|
88
|
+
object_id = state.store.import_page(
|
89
|
+
options[:template],
|
90
|
+
options[:template_page] || 1
|
91
|
+
)
|
92
|
+
page_options.merge!(object_id: object_id, page_template: true)
|
82
93
|
end
|
83
94
|
|
84
95
|
module ObjectStoreExtensions
|
@@ -96,32 +107,37 @@ module Prawn
|
|
96
107
|
#
|
97
108
|
def import_page(input, page_num)
|
98
109
|
@loaded_objects = {}
|
99
|
-
|
100
|
-
|
101
|
-
end
|
110
|
+
template_id = indexed_template(input, page_num)
|
111
|
+
return template_id if template_id
|
102
112
|
|
103
113
|
io = if input.respond_to?(:seek) && input.respond_to?(:read)
|
104
114
|
input
|
105
115
|
elsif File.file?(input.to_s)
|
106
116
|
StringIO.new(File.binread(input.to_s))
|
107
117
|
else
|
108
|
-
|
118
|
+
raise ArgumentError, 'input must be an IO-like object or a ' \
|
119
|
+
'filename'
|
109
120
|
end
|
110
121
|
|
111
122
|
hash = indexed_hash(input, io)
|
112
|
-
ref
|
123
|
+
ref = hash.page_references[page_num - 1]
|
113
124
|
|
114
125
|
if ref.nil?
|
115
126
|
nil
|
116
127
|
else
|
117
|
-
index_template(
|
128
|
+
index_template(
|
129
|
+
input, page_num,
|
130
|
+
load_object_graph(hash, ref).identifier
|
131
|
+
)
|
118
132
|
end
|
119
133
|
|
120
|
-
rescue PDF::Reader::MalformedPDFError,
|
121
|
-
|
134
|
+
rescue PDF::Reader::MalformedPDFError,
|
135
|
+
PDF::Reader::InvalidObjectError => e
|
136
|
+
msg = 'Error reading template file. If you are sure it\'s a valid PDF,'\
|
137
|
+
" it may be a bug.\n#{e.message}"
|
122
138
|
raise PDF::Core::Errors::TemplateError, msg
|
123
139
|
rescue PDF::Reader::UnsupportedFeatureError
|
124
|
-
msg =
|
140
|
+
msg = 'Template file contains unsupported PDF features'
|
125
141
|
raise PDF::Core::Errors::TemplateError, msg
|
126
142
|
end
|
127
143
|
|
@@ -181,8 +197,8 @@ module Prawn
|
|
181
197
|
#
|
182
198
|
def load_file(template)
|
183
199
|
unless (template.respond_to?(:seek) && template.respond_to?(:read)) ||
|
184
|
-
|
185
|
-
|
200
|
+
File.file?(template)
|
201
|
+
raise ArgumentError, "#{template} does not exist"
|
186
202
|
end
|
187
203
|
|
188
204
|
hash = PDF::Reader::ObjectHash.new(template)
|
@@ -191,8 +207,9 @@ module Prawn
|
|
191
207
|
@min_version = hash.pdf_version.to_f
|
192
208
|
|
193
209
|
if hash.trailer[:Encrypt]
|
194
|
-
msg =
|
195
|
-
|
210
|
+
msg = 'Template file is an encrypted PDF, it can\'t be used as a '\
|
211
|
+
'template'
|
212
|
+
raise PDF::Core::Errors::TemplateError, msg
|
196
213
|
end
|
197
214
|
|
198
215
|
if src_info
|
@@ -202,11 +219,13 @@ module Prawn
|
|
202
219
|
if src_root
|
203
220
|
@root = load_object_graph(hash, src_root).identifier
|
204
221
|
end
|
205
|
-
rescue PDF::Reader::MalformedPDFError,
|
206
|
-
|
222
|
+
rescue PDF::Reader::MalformedPDFError,
|
223
|
+
PDF::Reader::InvalidObjectError => e
|
224
|
+
msg = 'Error reading template file. If you are sure it\'s a valid PDF,'\
|
225
|
+
" it may be a bug.\n#{e.message}"
|
207
226
|
raise PDF::Core::Errors::TemplateError, msg
|
208
227
|
rescue PDF::Reader::UnsupportedFeatureError
|
209
|
-
msg =
|
228
|
+
msg = 'Template file contains unsupported PDF features'
|
210
229
|
raise PDF::Core::Errors::TemplateError, msg
|
211
230
|
end
|
212
231
|
|
@@ -220,7 +239,9 @@ module Prawn
|
|
220
239
|
@loaded_objects ||= {}
|
221
240
|
case object
|
222
241
|
when ::Hash then
|
223
|
-
object.each
|
242
|
+
object.each do |key, value|
|
243
|
+
object[key] = load_object_graph(hash, value)
|
244
|
+
end
|
224
245
|
object
|
225
246
|
when Array then
|
226
247
|
object.map { |item| load_object_graph(hash, item) }
|
@@ -228,7 +249,7 @@ module Prawn
|
|
228
249
|
unless @loaded_objects.key?(object.id)
|
229
250
|
@loaded_objects[object.id] = ref(nil)
|
230
251
|
new_obj = load_object_graph(hash, hash[object])
|
231
|
-
if new_obj.
|
252
|
+
if new_obj.is_a?(PDF::Reader::Stream)
|
232
253
|
stream_dict = load_object_graph(hash, new_obj.hash)
|
233
254
|
@loaded_objects[object.id].data = stream_dict
|
234
255
|
@loaded_objects[object.id] << new_obj.data
|
@@ -238,11 +259,11 @@ module Prawn
|
|
238
259
|
end
|
239
260
|
@loaded_objects[object.id]
|
240
261
|
when PDF::Reader::Stream
|
241
|
-
# Stream is a subclass of string, so this is here to prevent the
|
242
|
-
# being wrapped in a LiteralString
|
262
|
+
# Stream is a subclass of string, so this is here to prevent the
|
263
|
+
# stream being wrapped in a LiteralString
|
243
264
|
object
|
244
265
|
when String
|
245
|
-
|
266
|
+
utf8?(object) ? object : PDF::Core::ByteString.new(object)
|
246
267
|
else
|
247
268
|
object
|
248
269
|
end
|
@@ -251,7 +272,17 @@ module Prawn
|
|
251
272
|
end
|
252
273
|
end
|
253
274
|
|
254
|
-
Prawn::Document::VALID_OPTIONS
|
275
|
+
if Prawn::Document::VALID_OPTIONS.frozen?
|
276
|
+
Prawn::Document.const_set(
|
277
|
+
:VALID_OPTIONS,
|
278
|
+
(Prawn::Document.send(
|
279
|
+
:remove_const,
|
280
|
+
:VALID_OPTIONS
|
281
|
+
).dup << :template).freeze
|
282
|
+
)
|
283
|
+
else
|
284
|
+
Prawn::Document::VALID_OPTIONS << :template
|
285
|
+
end
|
255
286
|
Prawn::Document.extensions << Prawn::Templates
|
256
287
|
|
257
288
|
PDF::Core::ObjectStore.send(:include, Prawn::Templates::ObjectStoreExtensions)
|