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.
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,4 @@
1
+ module ThousandIsland
2
+ module Components
3
+ end
4
+ 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