prawn-templates 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/prawn/templates.rb +248 -0
- data/prawn-templates.gemspec +21 -0
- metadata +124 -0
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:
|