thousand_island 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/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/Guardfile +23 -0
- data/LICENSE.txt +22 -0
- data/README.md +363 -0
- data/Rakefile +1 -0
- data/lib/thousand_island.rb +21 -0
- data/lib/thousand_island/builder.rb +153 -0
- data/lib/thousand_island/components.rb +4 -0
- data/lib/thousand_island/components/base.rb +43 -0
- data/lib/thousand_island/components/body.rb +18 -0
- data/lib/thousand_island/components/footer.rb +85 -0
- data/lib/thousand_island/components/header.rb +24 -0
- data/lib/thousand_island/style_sheet.rb +112 -0
- data/lib/thousand_island/template.rb +280 -0
- data/lib/thousand_island/utilities/style_hash.rb +20 -0
- data/lib/thousand_island/utilities/utilities.rb +58 -0
- data/lib/thousand_island/version.rb +3 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/thousand_island/builder_spec.rb +106 -0
- data/spec/thousand_island/components/base_spec.rb +68 -0
- data/spec/thousand_island/components/body_spec.rb +37 -0
- data/spec/thousand_island/components/footer_spec.rb +57 -0
- data/spec/thousand_island/components/header_spec.rb +40 -0
- data/spec/thousand_island/style_sheet_spec.rb +44 -0
- data/spec/thousand_island/template_spec.rb +150 -0
- data/spec/thousand_island/utilities/style_hash_spec.rb +29 -0
- data/spec/thousand_island/utilities/utilities_spec.rb +82 -0
- data/spec/thousand_island_spec.rb +3 -0
- data/thousand_island.gemspec +30 -0
- metadata +200 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "thousand_island/version"
|
2
|
+
|
3
|
+
require "prawn"
|
4
|
+
|
5
|
+
require "thousand_island/style_sheet"
|
6
|
+
require "thousand_island/template"
|
7
|
+
require "thousand_island/builder"
|
8
|
+
|
9
|
+
require "thousand_island/components"
|
10
|
+
require "thousand_island/components/base"
|
11
|
+
require "thousand_island/components/header"
|
12
|
+
require "thousand_island/components/footer"
|
13
|
+
require "thousand_island/components/body"
|
14
|
+
|
15
|
+
require "thousand_island/utilities/utilities"
|
16
|
+
require "thousand_island/utilities/style_hash"
|
17
|
+
|
18
|
+
module ThousandIsland
|
19
|
+
Error = Class.new(StandardError)
|
20
|
+
TemplateRequiredError = Class.new(Error)
|
21
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
# Your Builder class is where you will put the necessary logic for
|
3
|
+
# rendering the final pdf. It's up to you how you get the data into
|
4
|
+
# the Builder. It will depend on the complexity. You might just pass
|
5
|
+
# an Invoice object (<code>MyBuilder.new(invoice))</code>) or you may have a
|
6
|
+
# bunch of methods that are called by an external object to get the
|
7
|
+
# data where it needs to be.
|
8
|
+
#
|
9
|
+
# You must declare which Template class you will be using. Failing
|
10
|
+
# to do so will raise a <code>TemplateRequiredError</code> when you call the
|
11
|
+
# build method. Declare the template with the following in the main
|
12
|
+
# class body:
|
13
|
+
#
|
14
|
+
# uses_template MyTemplate
|
15
|
+
#
|
16
|
+
# Your Builder can have a <code>filename</code> method, which will help a Rails
|
17
|
+
# Controller or other class determine the name to use to send the
|
18
|
+
# file to the browser or save to the filesystem (or both). Without
|
19
|
+
# this method it will have a default name, so you may choose to put
|
20
|
+
# the naming logic for your file elsewhere, it's up to you.
|
21
|
+
#
|
22
|
+
# You must have a <code>body_content</code> method that takes no arguments (or
|
23
|
+
# the pdf will be empty!). This is the method that is passed around
|
24
|
+
# internally in order for Prawn to render what is in the method.
|
25
|
+
# You can use raw Prawn syntax, or any of the style magic methods
|
26
|
+
# to render to the pdf. You may also call other methods from your
|
27
|
+
# <code>body_content</code> method, and use Prawn syntax and magic methods in
|
28
|
+
# those too.
|
29
|
+
#
|
30
|
+
# A Builder example might be:
|
31
|
+
#
|
32
|
+
# class MyBuilder < ThousandIsland::Builder
|
33
|
+
# uses_template MyTemplate
|
34
|
+
#
|
35
|
+
# attr_reader :data
|
36
|
+
#
|
37
|
+
# def initialize(data={})
|
38
|
+
# @data = data
|
39
|
+
# # do something with the data...
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def filename
|
43
|
+
# "Document#{data.id_number}"
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def body_content
|
47
|
+
# # call custom methods, magic methods or call Prawn methods directly:
|
48
|
+
# h1 'Main Heading'
|
49
|
+
# display_info
|
50
|
+
# body 'Main text in here...'
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # Custom method called by body_content
|
54
|
+
# def display_info
|
55
|
+
# body "Written by: #{data.author}"
|
56
|
+
# pdf.image data.avatar, height: 20
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# Finally, to get the finished pdf from your Builder, call the <code>build</code> method like so:
|
62
|
+
#
|
63
|
+
# pdf = my_builder.build
|
64
|
+
#
|
65
|
+
#
|
66
|
+
# Optional:
|
67
|
+
#
|
68
|
+
# Define a <code>header_content</code> method to add content below whatever is defined in the Template. This will be repeated according to the header settings in the Template.
|
69
|
+
#
|
70
|
+
# Define a <code>footer_content</code> method to add content above whatever is defined in the Template. This will be repeated according to the footer settings in the Template.
|
71
|
+
#
|
72
|
+
# Define a <code>settings</code> method that returns a Hash. This will be passed to the Template class and will override any of the Template default settings.
|
73
|
+
class Builder
|
74
|
+
|
75
|
+
attr_reader :pdf
|
76
|
+
|
77
|
+
class << self
|
78
|
+
attr_writer :template_klass
|
79
|
+
|
80
|
+
def template_klass
|
81
|
+
raise TemplateRequiredError.new 'Builders must set a Template class with #uses_template in the Class body' if @template_klass.nil?
|
82
|
+
@template_klass
|
83
|
+
end
|
84
|
+
|
85
|
+
def uses_template(klass)
|
86
|
+
self.template_klass = klass
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize(data=nil)
|
91
|
+
end
|
92
|
+
|
93
|
+
def filename
|
94
|
+
'default_filename'
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns the finished pdf ready to save to the filesystem or send back to the user's browser
|
98
|
+
def build
|
99
|
+
#body first so we know how many pages for repeated components
|
100
|
+
draw_body
|
101
|
+
draw_header
|
102
|
+
draw_footer
|
103
|
+
pdf.render
|
104
|
+
end
|
105
|
+
|
106
|
+
def settings
|
107
|
+
{}
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def draw_header
|
113
|
+
template.draw_header do
|
114
|
+
header_content if self.respond_to? :header_content
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def draw_body
|
119
|
+
template.draw_body do
|
120
|
+
body_content if respond_to? :body_content
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def draw_footer
|
125
|
+
template.draw_footer do
|
126
|
+
footer_content if respond_to? :footer_content
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def template
|
132
|
+
@template ||= begin
|
133
|
+
template = self.class.template_klass.new(settings)
|
134
|
+
@pdf = template.pdf
|
135
|
+
template
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#Respond to methods that relate to the style_sheet known styles
|
140
|
+
def method_missing(method_name, *arguments, &block)
|
141
|
+
if template.available_styles.include?(method_name)
|
142
|
+
template.send(method_name, *arguments)
|
143
|
+
else
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def respond_to_missing?(method_name, *)
|
149
|
+
template.available_styles.include?(method_name) || super
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
module Components
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :pdf, :options
|
6
|
+
|
7
|
+
def initialize(pdf, args=nil)
|
8
|
+
@options = args || self.defaults
|
9
|
+
@pdf = pdf
|
10
|
+
end
|
11
|
+
|
12
|
+
def draw(&block)
|
13
|
+
if repeated?
|
14
|
+
render_all &block
|
15
|
+
else
|
16
|
+
render &block
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_all(&block)
|
21
|
+
pdf.repeat :all do
|
22
|
+
render &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(&block)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def repeated?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.defaults
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
|
38
|
+
def defaults
|
39
|
+
self.class.defaults
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
module Components
|
3
|
+
class Body < Base
|
4
|
+
|
5
|
+
def render
|
6
|
+
start_coords = [0, options[:top]]
|
7
|
+
pdf.bounding_box(start_coords, height: options[:height], width: pdf.bounds.width) do
|
8
|
+
yield if block_given?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.defaults
|
13
|
+
{
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
module Components
|
3
|
+
class Footer < Base
|
4
|
+
|
5
|
+
def numbering_string
|
6
|
+
options[:numbering_string]
|
7
|
+
end
|
8
|
+
|
9
|
+
def number_pages?
|
10
|
+
options[:number_pages]
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(&block)
|
14
|
+
pdf.bounding_box([0, box_height], width: pdf.bounds.width, height: box_height) do
|
15
|
+
col1
|
16
|
+
col2(&block)
|
17
|
+
col3
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def col1_width
|
22
|
+
pdf.bounds.width * 0.15
|
23
|
+
end
|
24
|
+
|
25
|
+
def col2_width
|
26
|
+
pdf.bounds.width * 0.7
|
27
|
+
end
|
28
|
+
|
29
|
+
def col3_width
|
30
|
+
pdf.bounds.width * 0.15
|
31
|
+
end
|
32
|
+
|
33
|
+
def col1
|
34
|
+
end
|
35
|
+
|
36
|
+
def col2
|
37
|
+
start = col1_width
|
38
|
+
pdf.bounding_box([start, box_height], width: col2_width, height: box_height) do
|
39
|
+
options[:style].each do |k,v|
|
40
|
+
pdf.send(k, v) if pdf.respond_to?(k)
|
41
|
+
end if options[:style]
|
42
|
+
yield if block_given?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def col3
|
47
|
+
start = col1_width + col2_width
|
48
|
+
pdf.bounding_box([start, box_height], width: col3_width, height: box_height) do
|
49
|
+
pdf.number_pages numbering_string, numbering_options if number_pages?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def box_height
|
54
|
+
options[:height]
|
55
|
+
end
|
56
|
+
|
57
|
+
def numbering_options
|
58
|
+
options[:style].merge(options[:numbering_options])
|
59
|
+
end
|
60
|
+
|
61
|
+
def repeated?
|
62
|
+
options[:repeated]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.defaults
|
66
|
+
{
|
67
|
+
height: 33,
|
68
|
+
top_padding: 20,
|
69
|
+
repeated: true,
|
70
|
+
numbering_options: default_numbering_options,
|
71
|
+
number_pages: true,
|
72
|
+
numbering_string: '<page>',
|
73
|
+
style: {},
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.default_numbering_options
|
78
|
+
{
|
79
|
+
align: :right,
|
80
|
+
start_count_at: 1,
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
module Components
|
3
|
+
class Header < Base
|
4
|
+
|
5
|
+
def render
|
6
|
+
pdf.bounding_box([0, pdf.bounds.height], width: pdf.bounds.width, height: options[:height]) do
|
7
|
+
yield if block_given?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def repeated?
|
12
|
+
options[:repeated]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.defaults
|
16
|
+
{
|
17
|
+
height: 33,
|
18
|
+
bottom_padding: 20,
|
19
|
+
repeated: true
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module ThousandIsland
|
2
|
+
# The StyleSheet is designed to be a mixin to the Template class. It may also
|
3
|
+
# be included into other modules to define custom StyleSheets.
|
4
|
+
#
|
5
|
+
# Methods should return a StyleHash object rather than a vanilla Hash, as it
|
6
|
+
# has some customisation to help it work with Prawn. The default_style is
|
7
|
+
# used as the starting point for all other styles. For instance, the
|
8
|
+
# <code>default_style[:size]</code> value is multiplied in the heading styles,
|
9
|
+
# so changing the default style size value will have a cascading effect. Check
|
10
|
+
# the source for the default values and override as preferred.
|
11
|
+
#
|
12
|
+
# An example of a custom StyleSheet:
|
13
|
+
#
|
14
|
+
# module MyStyleSheet
|
15
|
+
# include ThousandIsland::StyleSheet
|
16
|
+
#
|
17
|
+
# def default_style
|
18
|
+
# super.merge({
|
19
|
+
# size: 12,
|
20
|
+
# color: '222222'
|
21
|
+
# })
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def h1_style
|
25
|
+
# super.merge({ align: :center })
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
module StyleSheet
|
31
|
+
|
32
|
+
def default_style
|
33
|
+
StyleHash.new({
|
34
|
+
size: 10,
|
35
|
+
style: :normal,
|
36
|
+
align: :left,
|
37
|
+
leading: 1,
|
38
|
+
inline_format: true,
|
39
|
+
color: '000000',
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
def body_style
|
44
|
+
default_style
|
45
|
+
end
|
46
|
+
|
47
|
+
def h1_style
|
48
|
+
default_style.merge({
|
49
|
+
size: default_style[:size] * 1.8,
|
50
|
+
style: :bold,
|
51
|
+
leading: 8,
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
def h2_style
|
56
|
+
default_style.merge({
|
57
|
+
size: default_style[:size] * 1.5,
|
58
|
+
style: :bold,
|
59
|
+
leading: 4,
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
def h3_style
|
64
|
+
default_style.merge({
|
65
|
+
size: default_style[:size] * 1.4,
|
66
|
+
style: :bold,
|
67
|
+
leading: 4,
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
def h4_style
|
72
|
+
default_style.merge({
|
73
|
+
size: default_style[:size] * 1.1,
|
74
|
+
style: :bold_italic,
|
75
|
+
leading: 4,
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
def h5_style
|
80
|
+
default_style.merge({
|
81
|
+
size: default_style[:size] * 1,
|
82
|
+
leading: 4,
|
83
|
+
})
|
84
|
+
end
|
85
|
+
|
86
|
+
def h6_style
|
87
|
+
default_style.merge({
|
88
|
+
size: default_style[:size] * 0.85,
|
89
|
+
style: :italic,
|
90
|
+
leading: 4,
|
91
|
+
})
|
92
|
+
end
|
93
|
+
|
94
|
+
def footer_style
|
95
|
+
default_style.merge({
|
96
|
+
size: default_style[:size] * 0.8,
|
97
|
+
color: '666666',
|
98
|
+
align: :center,
|
99
|
+
})
|
100
|
+
end
|
101
|
+
|
102
|
+
available_styles = []
|
103
|
+
instance_methods.grep(/_style$/).each do |method_name|
|
104
|
+
style = method_name.to_s.sub('_style', '')
|
105
|
+
available_styles << style.to_sym unless style == 'default'
|
106
|
+
end
|
107
|
+
define_method(:available_styles) do
|
108
|
+
available_styles
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|