prawn-templates 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5676d0def6b3ed271abab94fd8975be30f3f44e
4
+ data.tar.gz: 6bae0d6a3e5afdfb10b6145310128ec1d667708a
5
+ SHA512:
6
+ metadata.gz: 93971ea890bc2d63697a10a5dcc4292f42eec991a7055ee9801a81584a6f97ffef1b2ac5d852030010e5fab1daf59883633843b5921a7cae2e70339c14c95ba1
7
+ data.tar.gz: 90641c2e47447b22a31618e506ad800e520047960e1fdf517f0021a67dc725861c33f8cd36da6f4271d4f399f874399a7d1b2785eeb2e0c27ea01276d2130858
@@ -0,0 +1,248 @@
1
+ # This is free software. See LICENSE and COPYING files for details.
2
+
3
+ module Prawn
4
+ # @private
5
+ module Templates
6
+ def initialize_first_page(options)
7
+ return super unless options[:template]
8
+
9
+ fresh_content_streams(options)
10
+ go_to_page(1)
11
+ end
12
+
13
+ ## FIXME: This is going to be terribly brittle because
14
+ # it copy-pastes the start_new_page method. But at least
15
+ # it should only run when templates are used.
16
+
17
+ def start_new_page(options = {})
18
+ return super unless options[:template]
19
+
20
+ if last_page = state.page
21
+ last_page_size = last_page.size
22
+ last_page_layout = last_page.layout
23
+ last_page_margins = last_page.margins
24
+ end
25
+
26
+ page_options = {:size => options[:size] || last_page_size,
27
+ :layout => options[:layout] || last_page_layout,
28
+ :margins => last_page_margins}
29
+ if last_page
30
+ new_graphic_state = last_page.graphic_state.dup if last_page.graphic_state
31
+ #erase the color space so that it gets reset on new page for fussy pdf-readers
32
+ new_graphic_state.color_space = {} if new_graphic_state
33
+ page_options.merge!(:graphic_state => new_graphic_state)
34
+ end
35
+
36
+ merge_template_options(page_options, options)
37
+
38
+ state.page = PDF::Core::Page.new(self, page_options)
39
+
40
+ apply_margin_options(options)
41
+ generate_margin_box
42
+
43
+ # Reset the bounding box if the new page has different size or layout
44
+ if last_page && (last_page.size != state.page.size ||
45
+ last_page.layout != state.page.layout)
46
+ @bounding_box = @margin_box
47
+ end
48
+
49
+ state.page.new_content_stream
50
+ use_graphic_settings(true)
51
+ forget_text_rendering_mode!
52
+
53
+ unless options[:orphan]
54
+ state.insert_page(state.page, @page_number)
55
+ @page_number += 1
56
+
57
+ canvas { image(@background, :scale => @background_scale, :at => bounds.top_left) } if @background
58
+ @y = @bounding_box.absolute_top
59
+
60
+ float do
61
+ state.on_page_create_action(self)
62
+ end
63
+ end
64
+ end
65
+
66
+ def merge_template_options(page_options, options)
67
+ object_id = state.store.import_page(options[:template], options[:template_page] || 1)
68
+ page_options.merge!(:object_id => object_id, :page_template => true)
69
+ end
70
+
71
+ module ObjectStoreExtensions
72
+ # imports all objects required to render a page from another PDF. The
73
+ # objects are added to the current object store, but NOT linked
74
+ # anywhere.
75
+ #
76
+ # The object ID of the root Page object is returned, it's up to the
77
+ # calling code to link that into the document structure somewhere. If
78
+ # this isn't done the imported objects will just be removed when the
79
+ # store is compacted.
80
+ #
81
+ # Imports nothing and returns nil if the requested page number doesn't
82
+ # exist. page_num is 1 indexed, so 1 indicates the first page.
83
+ #
84
+ def import_page(input, page_num)
85
+ @loaded_objects = {}
86
+ if template_id = indexed_template(input, page_num)
87
+ return template_id
88
+ end
89
+
90
+ io = if input.respond_to?(:seek) && input.respond_to?(:read)
91
+ input
92
+ elsif File.file?(input.to_s)
93
+ StringIO.new(File.binread(input.to_s))
94
+ else
95
+ raise ArgumentError, "input must be an IO-like object or a filename"
96
+ end
97
+
98
+ # unless File.file?(filename)
99
+ # raise ArgumentError, "#{filename} does not exist"
100
+ # end
101
+
102
+ hash = indexed_hash(input, io)
103
+ ref = hash.page_references[page_num - 1]
104
+
105
+ if ref.nil?
106
+ nil
107
+ else
108
+ index_template(input, page_num, load_object_graph(hash, ref).identifier)
109
+ end
110
+
111
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError => e
112
+ msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug.\n#{e.message}"
113
+ raise PDF::Core::Errors::TemplateError, msg
114
+ rescue PDF::Reader::UnsupportedFeatureError
115
+ msg = "Template file contains unsupported PDF features"
116
+ raise PDF::Core::Errors::TemplateError, msg
117
+ end
118
+
119
+ private
120
+
121
+ # An index for page templates so that their loaded object graph
122
+ # can be reused without multiple loading
123
+ def template_index
124
+ @template_index ||= {}
125
+ end
126
+
127
+ # returns the indexed object graph identifier for a template page if
128
+ # it exists
129
+ def indexed_template(input, page_number)
130
+ key = indexing_key(input)
131
+ template_index[key] && template_index[key][page_number]
132
+ end
133
+
134
+ # indexes the identifier for a page from a template
135
+ def index_template(input, page_number, id)
136
+ (template_index[indexing_key(input)] ||= {})[page_number] ||= id
137
+ end
138
+
139
+ # An index for the read object hash of a pdf template so that the
140
+ # object hash does not need to be parsed multiple times when using
141
+ # different pages of the pdf as page templates
142
+ def hash_index
143
+ @hash_index ||= {}
144
+ end
145
+
146
+ # reads and indexes a new IO for a template
147
+ # if the IO has been indexed already then the parsed object hash
148
+ # is returned directly
149
+ def indexed_hash(input, io)
150
+ hash_index[indexing_key(input)] ||= PDF::Reader::ObjectHash.new(io)
151
+ end
152
+
153
+ # the index key for the input.
154
+ # uses object_id so that both a string filename or an IO stream can be
155
+ # indexed and reused provided the same object gets used in multiple page
156
+ # template calls.
157
+ def indexing_key(input)
158
+ input.object_id
159
+ end
160
+
161
+ # returns a nested array of object IDs for all pages in this object store.
162
+ #
163
+ def get_page_objects(obj)
164
+ if obj.data[:Type] == :Page
165
+ obj.identifier
166
+ elsif obj.data[:Type] == :Pages
167
+ obj.data[:Kids].map { |kid| get_page_objects(kid) }
168
+ end
169
+ end
170
+
171
+ # takes a source PDF and uses it as a template for this document.
172
+ #
173
+ def load_file(template)
174
+ unless (template.respond_to?(:seek) && template.respond_to?(:read)) ||
175
+ File.file?(template)
176
+ raise ArgumentError, "#{template} does not exist"
177
+ end
178
+
179
+ hash = PDF::Reader::ObjectHash.new(template)
180
+ src_info = hash.trailer[:Info]
181
+ src_root = hash.trailer[:Root]
182
+ @min_version = hash.pdf_version.to_f
183
+
184
+ if hash.trailer[:Encrypt]
185
+ msg = "Template file is an encrypted PDF, it can't be used as a template"
186
+ raise PDF::Core::Errors::TemplateError, msg
187
+ end
188
+
189
+ if src_info
190
+ @info = load_object_graph(hash, src_info).identifier
191
+ end
192
+
193
+ if src_root
194
+ @root = load_object_graph(hash, src_root).identifier
195
+ end
196
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError => e
197
+ msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug.\n#{e.message}"
198
+ raise PDF::Core::Errors::TemplateError, msg
199
+ rescue PDF::Reader::UnsupportedFeatureError
200
+ msg = "Template file contains unsupported PDF features"
201
+ raise PDF::Core::Errors::TemplateError, msg
202
+ end
203
+
204
+ # recurse down an object graph from a source PDF, importing all the
205
+ # indirect objects we find.
206
+ #
207
+ # hash is the PDF::Reader::ObjectHash to extract objects from, object is
208
+ # the object to extract.
209
+ #
210
+ def load_object_graph(hash, object)
211
+ @loaded_objects ||= {}
212
+ case object
213
+ when ::Hash then
214
+ object.each { |key,value| object[key] = load_object_graph(hash, value) }
215
+ object
216
+ when Array then
217
+ object.map { |item| load_object_graph(hash, item)}
218
+ when PDF::Reader::Reference then
219
+ unless @loaded_objects.has_key?(object.id)
220
+ @loaded_objects[object.id] = ref(nil)
221
+ new_obj = load_object_graph(hash, hash[object])
222
+ if new_obj.kind_of?(PDF::Reader::Stream)
223
+ stream_dict = load_object_graph(hash, new_obj.hash)
224
+ @loaded_objects[object.id].data = stream_dict
225
+ @loaded_objects[object.id] << new_obj.data
226
+ else
227
+ @loaded_objects[object.id].data = new_obj
228
+ end
229
+ end
230
+ @loaded_objects[object.id]
231
+ when PDF::Reader::Stream
232
+ # Stream is a subclass of string, so this is here to prevent the stream
233
+ # being wrapped in a LiteralString
234
+ object
235
+ when String
236
+ is_utf8?(object) ? object : PDF::Core::ByteString.new(object)
237
+ else
238
+ object
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ Prawn::Document::VALID_OPTIONS << :template
246
+ Prawn::Document.extensions << Prawn::Templates
247
+
248
+ PDF::Core::ObjectStore.send(:include, Prawn::Templates::ObjectStoreExtensions)
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "prawn-templates"
3
+ spec.version = File.read(File.expand_path('VERSION', File.dirname(__FILE__))).strip
4
+ spec.platform = Gem::Platform::RUBY
5
+ spec.summary = "Prawn::Templates allows using PDFs as templates in Prawn"
6
+ spec.files = Dir.glob("{lib}/**/**/*") +
7
+ ["prawn-templates.gemspec"]
8
+ spec.require_path = "lib"
9
+ spec.required_ruby_version = '>= 1.9.3'
10
+ spec.required_rubygems_version = ">= 1.3.6"
11
+
12
+ spec.authors = ["Gregory Brown","Brad Ediger","Daniel Nelson","Jonathan Greenberg","James Healy"]
13
+ spec.email = ["gregory.t.brown@gmail.com","brad@bradediger.com","dnelson@bluejade.com","greenberg@entryway.net","jimmy@deefa.com"]
14
+ spec.add_dependency('pdf-reader', '~>1.2')
15
+ spec.add_dependency('prawn', '>= 0.15.0')
16
+ spec.add_development_dependency('pdf-inspector', '~> 1.1.0')
17
+ spec.add_development_dependency('rspec')
18
+ spec.add_development_dependency('rake')
19
+ spec.homepage = "http://prawn.majesticseacreature.com"
20
+ spec.description = "Prawn::Templates allows using PDFs as templates in Prawn"
21
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prawn-templates
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gregory Brown
8
+ - Brad Ediger
9
+ - Daniel Nelson
10
+ - Jonathan Greenberg
11
+ - James Healy
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+ date: 2014-01-21 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: pdf-reader
19
+ requirement: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: '1.2'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '1.2'
31
+ - !ruby/object:Gem::Dependency
32
+ name: prawn
33
+ requirement: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.15.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - '>='
43
+ - !ruby/object:Gem::Version
44
+ version: 0.15.0
45
+ - !ruby/object:Gem::Dependency
46
+ name: pdf-inspector
47
+ requirement: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: 1.1.0
52
+ type: :development
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ version: 1.1.0
59
+ - !ruby/object:Gem::Dependency
60
+ name: rspec
61
+ requirement: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rake
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ description: Prawn::Templates allows using PDFs as templates in Prawn
88
+ email:
89
+ - gregory.t.brown@gmail.com
90
+ - brad@bradediger.com
91
+ - dnelson@bluejade.com
92
+ - greenberg@entryway.net
93
+ - jimmy@deefa.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - lib/prawn/templates.rb
99
+ - prawn-templates.gemspec
100
+ homepage: http://prawn.majesticseacreature.com
101
+ licenses: []
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: 1.9.3
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: 1.3.6
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.0.14
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Prawn::Templates allows using PDFs as templates in Prawn
123
+ test_files: []
124
+ has_rdoc: