thousand_island 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 +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
|