prawn-templates 0.0.1

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