jerryvos-prawn-layout 0.2.0.3 → 0.2.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/testtask'
4
4
  require "rake/rdoctask"
5
5
  require "rake/gempackagetask"
6
6
 
7
- PRAWN_LAYOUT_VERSION = "0.2.0.3"
7
+ PRAWN_LAYOUT_VERSION = "0.2.0.4"
8
8
 
9
9
  task :default => [:test]
10
10
 
@@ -0,0 +1,238 @@
1
+ module Prawn
2
+ class Document
3
+ # Defines the grid system for a particular document. Takes the number of rows and columns and the
4
+ # width to use for the gutter as the keys :rows, :columns, :gutter
5
+ #
6
+ def define_grid(options = {})
7
+ @grid = Grid.new(self, options)
8
+ end
9
+
10
+ # A method that can either be used to access a particular grid on the page or interogate the grid
11
+ # system directly.
12
+ #
13
+ # @pdf.grid # Get the Grid directly
14
+ # @pdf.grid([0,1]) # Get the box at [0,1]
15
+ # @pdf.grid([0,1], [1,2]) # Get a multi-box spanning from [0,1] to [1,2]
16
+ def grid(*args)
17
+ @boxes ||= {}
18
+ @boxes[args] ||= if args.empty?
19
+ @grid
20
+ else
21
+ g1, g2 = args
22
+ if(g1.class == Array && g2.class == Array &&
23
+ g1.length == 2 && g2.length == 2)
24
+ multi_box(single_box(*g1), single_box(*g2))
25
+ else
26
+ single_box(g1, g2)
27
+ end
28
+ end
29
+ end
30
+
31
+ # A Grid represents the entire grid system of a Page and calculates the column width and row height
32
+ # of the base box.
33
+ class Grid
34
+ attr_reader :pdf, :columns, :rows, :gutter
35
+ # :nodoc
36
+ def initialize(pdf, options = {})
37
+ Prawn.verify_options([:columns, :rows, :gutter], options)
38
+
39
+ @pdf = pdf
40
+ @columns = options[:columns]
41
+ @rows = options[:rows]
42
+ @gutter = options[:gutter].to_f
43
+ end
44
+
45
+ # Calculates the base width of boxes.
46
+ def column_width
47
+ @column_width ||= subdivide(pdf.bounds.width, columns)
48
+ end
49
+
50
+ # Calculates the base height of boxes.
51
+ def row_height
52
+ @row_height ||= subdivide(pdf.bounds.height, rows)
53
+ end
54
+
55
+ # Diagnostic tool to show all of the grids. Defaults to gray.
56
+ def show_all(color = "CCCCCC")
57
+ self.rows.times do |i|
58
+ self.columns.times do |j|
59
+ pdf.grid(i,j).show(color)
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+ def subdivide(total, num)
66
+ (total.to_f - (gutter * (num - 1).to_f)) / num.to_f
67
+ end
68
+ end
69
+
70
+ # A Box is a class that represents a bounded area of a page. A Grid object has methods that allow
71
+ # easy access to the coordinates of its corners, which can be plugged into most existing prawn
72
+ # methods.
73
+ #
74
+ class Box
75
+ attr_reader :pdf
76
+
77
+ def initialize(pdf, i, j)
78
+ @pdf = pdf
79
+ @i = i
80
+ @j = j
81
+ end
82
+
83
+ # Mostly diagnostic method that outputs the name of a box as col_num, row_num
84
+ def name
85
+ "#{@i.to_s},#{@j.to_s}"
86
+ end
87
+
88
+ # :nodoc
89
+ def total_height
90
+ pdf.bounds.height.to_f
91
+ end
92
+
93
+ # Width of a box
94
+ def width
95
+ grid.column_width.to_f
96
+ end
97
+
98
+ # Height of a box
99
+ def height
100
+ grid.row_height.to_f
101
+ end
102
+
103
+ # Width of the gutter
104
+ def gutter
105
+ grid.gutter.to_f
106
+ end
107
+
108
+ # x-coordinate of left side
109
+ def left
110
+ @left ||= (width + gutter) * @j.to_f
111
+ end
112
+
113
+ # x-coordinate of right side
114
+ def right
115
+ @right ||= left + width
116
+ end
117
+
118
+ # y-coordinate of the top
119
+ def top
120
+ @top ||= total_height - ((height + gutter) * @i.to_f)
121
+ end
122
+
123
+ # y-coordinate of the bottom
124
+ def bottom
125
+ @bottom ||= top - height
126
+ end
127
+
128
+ # x,y coordinates of top left corner
129
+ def top_left
130
+ [left, top]
131
+ end
132
+
133
+ # x,y coordinates of top right corner
134
+ def top_right
135
+ [right, top]
136
+ end
137
+
138
+ # x,y coordinates of bottom left corner
139
+ def bottom_left
140
+ [left, bottom]
141
+ end
142
+
143
+ # x,y coordinates of bottom right corner
144
+ def bottom_right
145
+ [right, bottom]
146
+ end
147
+
148
+ # Creates a standard bounding box based on the grid box.
149
+ def bounding_box(&blk)
150
+ pdf.bounding_box(top_left, :width => width, :height => height, &blk)
151
+ end
152
+
153
+ # Diagnostic method
154
+ def show(grid_color = "CCCCCC")
155
+ self.bounding_box do
156
+ pdf.stroke_color = grid_color
157
+ pdf.text self.name
158
+ pdf.stroke_bounds
159
+ end
160
+ end
161
+
162
+ private
163
+ def grid
164
+ pdf.grid
165
+ end
166
+ end
167
+
168
+ # A MultiBox is specified by 2 Boxes and spans the areas between.
169
+ class MultiBox < Box
170
+ def initialize(pdf, b1, b2)
171
+ @pdf = pdf
172
+ @bs = [b1, b2]
173
+ end
174
+
175
+ def name
176
+ @bs.map {|b| b.name}.join(":")
177
+ end
178
+
179
+ def total_height
180
+ @bs[0].total_height
181
+ end
182
+
183
+ def width
184
+ right_box.right - left_box.left
185
+ end
186
+
187
+ def height
188
+ top_box.top - bottom_box.bottom
189
+ end
190
+
191
+ def gutter
192
+ @bs[0].gutter
193
+ end
194
+
195
+ def left
196
+ left_box.left
197
+ end
198
+
199
+ def right
200
+ right_box.right
201
+ end
202
+
203
+ def top
204
+ top_box.top
205
+ end
206
+
207
+ def bottom
208
+ bottom_box.bottom
209
+ end
210
+
211
+ private
212
+ def left_box
213
+ @left_box ||= @bs.min {|a,b| a.left <=> b.left}
214
+ end
215
+
216
+ def right_box
217
+ @right_box ||= @bs.max {|a,b| a.right <=> b.right}
218
+ end
219
+
220
+ def top_box
221
+ @top_box ||= @bs.max {|a,b| a.top <=> b.top}
222
+ end
223
+
224
+ def bottom_box
225
+ @bottom_box ||= @bs.min {|a,b| a.bottom <=> b.bottom}
226
+ end
227
+ end
228
+
229
+ private
230
+ def single_box(i, j)
231
+ Box.new(self, i, j)
232
+ end
233
+
234
+ def multi_box(b1, b2)
235
+ MultiBox.new(self, b1, b2)
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ # layout/page.rb : Provides helpers for page layout
4
+ #
5
+ # Copyright January 2009, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ class Document
11
+ # A LazyBoundingBox is simply a BoundingBox with an action tied to it to be
12
+ # executed later. The lazy_bounding_box method takes the same arguments as
13
+ # bounding_box, but returns a LazyBoundingBox object instead of executing
14
+ # the code block directly.
15
+ #
16
+ # You can then call LazyBoundingBox#draw at any time (or multiple times if
17
+ # you wish), and the contents of the block will then be run. This can be
18
+ # useful for assembling repeating page elements or reusable components.
19
+ #
20
+ # file = "lazy_bounding_boxes.pdf"
21
+ # Prawn::Document.generate(file, :skip_page_creation => true) do
22
+ # point = [bounds.right-50, bounds.bottom + 25]
23
+ # page_counter = lazy_bounding_box(point, :width => 50) do
24
+ # text "Page: #{page_count}"
25
+ # end
26
+ #
27
+ # 10.times do
28
+ # start_new_page
29
+ # text "Some text"
30
+ # page_counter.draw
31
+ # end
32
+ # end
33
+ #
34
+ def lazy_bounding_box(*args,&block)
35
+ translate!(args[0])
36
+ box = LazyBoundingBox.new(self,*args)
37
+ box.action(&block)
38
+ return box
39
+ end
40
+
41
+ # A bounding box with the same dimensions of its parents, minus a margin
42
+ # on all sides
43
+ #
44
+ def padded_box(margin, &block)
45
+ bounding_box [bounds.left + margin, bounds.top - margin],
46
+ :width => bounds.width - (margin * 2),
47
+ :height => bounds.height - (margin * 2), &block
48
+ end
49
+
50
+ # A header is a LazyBoundingBox drawn relative to the margins that can be
51
+ # repeated on every page of the document.
52
+ #
53
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
54
+ # width and height are used.
55
+ #
56
+ # header margin_box.top_left do
57
+ # text "Here's My Fancy Header", :size => 25, :align => :center
58
+ # stroke_horizontal_rule
59
+ # end
60
+ #
61
+ def header(top_left,options={},&block)
62
+ @header = repeating_page_element(top_left,options,&block)
63
+ end
64
+
65
+ # A footer is a LazyBoundingBox drawn relative to the margins that can be
66
+ # repeated on every page of the document.
67
+ #
68
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
69
+ # width and height are used.
70
+ #
71
+ # footer [margin_box.left, margin_box.bottom + 25] do
72
+ # stroke_horizontal_rule
73
+ # text "And here's a sexy footer", :size => 16
74
+ # end
75
+ #
76
+ def footer(top_left,options={},&block)
77
+ @footer = repeating_page_element(top_left,options,&block)
78
+ end
79
+
80
+ private
81
+
82
+ def repeating_page_element(top_left,options={},&block)
83
+ r = LazyBoundingBox.new(self, translate(top_left),
84
+ :width => options[:width] || margin_box.width,
85
+ :height => options[:height] || margin_box.height )
86
+ r.action(&block)
87
+ return r
88
+ end
89
+
90
+ class LazyBoundingBox < BoundingBox
91
+
92
+ # Defines the block to be executed by LazyBoundingBox#draw.
93
+ # Usually, this will be used via a higher level interface.
94
+ # See the documentation for Document#lazy_bounding_box, Document#header,
95
+ # and Document#footer
96
+ #
97
+ def action(&block)
98
+ @action = block
99
+ end
100
+
101
+ # Sets Document#bounds to use the LazyBoundingBox for its bounds,
102
+ # runs the block specified by LazyBoundingBox#action,
103
+ # and then restores the original bounds of the document.
104
+ #
105
+ def draw
106
+ @parent.mask(:y) do
107
+ parent_box = @parent.bounds
108
+ @parent.bounds = self
109
+ @parent.y = absolute_top
110
+ @action.call
111
+ @parent.bounds = parent_box
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,21 @@
1
+ require "prawn/table"
2
+ require "prawn/layout/page"
3
+ require 'prawn/layout/grid'
4
+
5
+ module Prawn
6
+
7
+ module Errors
8
+
9
+ # This error is raised when table data is malformed
10
+ #
11
+ InvalidTableData = Class.new(StandardError)
12
+
13
+ # This error is raised when an empty or nil table is rendered
14
+ #
15
+ EmptyTable = Class.new(StandardError)
16
+ end
17
+
18
+ module Layout
19
+ VERSION = "0.2.0.4"
20
+ end
21
+ end
@@ -0,0 +1,287 @@
1
+ # encoding: utf-8
2
+
3
+ # cell.rb : Table support functions
4
+ #
5
+ # Copyright June 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ module Prawn
9
+
10
+ class Document
11
+ # Builds and renders a Table::Cell. A cell is essentially a
12
+ # special-purpose bounding box designed for flowing text within a bordered
13
+ # area. For available options, see Table::Cell#new.
14
+ #
15
+ # Prawn::Document.generate("cell.pdf") do
16
+ # cell [100,500],
17
+ # :width => 200,
18
+ # :text => "The rain in Spain falls mainly on the plains"
19
+ # end
20
+ #
21
+ def cell(point, options={})
22
+ Prawn::Table::Cell.new(
23
+ options.merge(:document => self, :point => point)).draw
24
+ end
25
+ end
26
+
27
+ class Table
28
+ # A cell is a special-purpose bounding box designed to flow text within a
29
+ # bordered area. This is used by Prawn's Document::Table implementation but
30
+ # can also be used standalone for drawing text boxes via Document#cell
31
+ #
32
+ class Cell
33
+
34
+ # Creates a new cell object. Generally used indirectly via Document#cell
35
+ #
36
+ # Of the available options listed below, <tt>:point</tt>, <tt>:width</tt>,
37
+ # and <tt>:text</tt> must be provided. If you are not using the
38
+ # Document#cell shortcut, the <tt>:document</tt> must also be provided.
39
+ #
40
+ # <tt>:point</tt>:: Absolute [x,y] coordinate of the top-left corner of the cell.
41
+ # <tt>:document</tt>:: The Prawn::Document object to render on.
42
+ # <tt>:text</tt>:: The text to be flowed within the cell
43
+ # <tt>:text_color</tt>:: The color of the text to be displayed
44
+ # <tt>:width</tt>:: The width in PDF points of the cell.
45
+ # <tt>:height</tt>:: The height in PDF points of the cell.
46
+ # <tt>:horizontal_padding</tt>:: The horizontal padding in PDF points
47
+ # <tt>:vertical_padding</tt>:: The vertical padding in PDF points
48
+ # <tt>:padding</tt>:: Overrides both horizontal and vertical padding
49
+ # <tt>:align</tt>:: One of <tt>:left</tt>, <tt>:right</tt>, <tt>:center</tt>
50
+ # <tt>:borders</tt>:: An array of sides which should have a border. Any of <tt>:top</tt>, <tt>:left</tt>, <tt>:right</tt>, <tt>:bottom</tt>
51
+ # <tt>:border_width</tt>:: The border line width. Defaults to 1.
52
+ # <tt>:border_style</tt>:: One of <tt>:all</tt>, <tt>:no_top</tt>, <tt>:no_bottom</tt>, <tt>:sides</tt>, <tt>:none</tt>, <tt>:bottom_only</tt>. Defaults to :all.
53
+ # <tt>:border_color</tt>:: The color of the cell border.
54
+ # <tt>:font_size</tt>:: The font size for the cell text.
55
+ # <tt>:font_style</tt>:: The font style for the cell text.
56
+ #
57
+ def initialize(options={})
58
+ @point = options[:point]
59
+ @document = options[:document]
60
+ @text = options[:text].to_s
61
+ @text_color = options[:text_color]
62
+ @width = options[:width]
63
+ @height = options[:height]
64
+ @borders = options[:borders]
65
+ @border_width = options[:border_width] || 1
66
+ @border_style = options[:border_style] || :all
67
+ @border_color = options[:border_color]
68
+ @background_color = options[:background_color]
69
+ @align = options[:align] || :left
70
+ @font_size = options[:font_size]
71
+ @font_style = options[:font_style]
72
+
73
+ @horizontal_padding = options[:horizontal_padding] || 0
74
+ @vertical_padding = options[:vertical_padding] || 0
75
+
76
+ if options[:padding]
77
+ @horizontal_padding = @vertical_padding = options[:padding]
78
+ end
79
+
80
+ @rowspan = options[:rowspan] || 1
81
+ @colspan = options[:colspan] || 1
82
+ end
83
+
84
+ attr_accessor :point, :border_style, :border_width, :background_color,
85
+ :document, :horizontal_padding, :vertical_padding, :align,
86
+ :borders, :text_color, :border_color, :font_size, :font_style,
87
+ :rowspan, :colspan
88
+
89
+ attr_writer :height, :width #:nodoc:
90
+
91
+ # Returns the cell's text as a string.
92
+ #
93
+ def to_s
94
+ @text
95
+ end
96
+
97
+ # The width of the text area excluding the horizonal padding
98
+ #
99
+ def text_area_width
100
+ width - 2*@horizontal_padding
101
+ end
102
+
103
+ # The width of the cell in PDF points
104
+ #
105
+ def width
106
+ @width || (@document.width_of(@text, :size => @font_size)) + 2*@horizontal_padding
107
+ end
108
+
109
+ # The height of the cell in PDF points
110
+ #
111
+ def height
112
+ @height || text_area_height + 2*@vertical_padding
113
+ end
114
+
115
+ # The height of the text area excluding the vertical padding
116
+ #
117
+ def text_area_height
118
+ text_height = 0
119
+ if @font_size
120
+ @document.font_size(@font_size) do
121
+ text_height = @document.height_of(@text, text_area_width)
122
+ end
123
+ else
124
+ text_height = @document.height_of(@text, text_area_width)
125
+ end
126
+ text_height
127
+ end
128
+
129
+ # Draws the cell onto the PDF document
130
+ #
131
+ def draw
132
+ rel_point = @point
133
+
134
+ if @background_color
135
+ @document.mask(:fill_color) do
136
+ @document.fill_color @background_color
137
+ h = borders.include?(:bottom) ?
138
+ height - border_width : height + border_width / 2.0
139
+ @document.fill_rectangle [rel_point[0] + border_width / 2.0,
140
+ rel_point[1] - border_width / 2.0 ],
141
+ width - border_width, h
142
+ end
143
+ end
144
+
145
+ if @border_width > 0
146
+ @document.mask(:line_width) do
147
+ @document.line_width = @border_width
148
+
149
+ @document.mask(:stroke_color) do
150
+ @document.stroke_color @border_color if @border_color
151
+
152
+ if borders.include?(:left)
153
+ @document.stroke_line [rel_point[0], rel_point[1] + (@border_width / 2.0)],
154
+ [rel_point[0], rel_point[1] - height - @border_width / 2.0 ]
155
+ end
156
+
157
+ if borders.include?(:right)
158
+ @document.stroke_line(
159
+ [rel_point[0] + width, rel_point[1] + (@border_width / 2.0)],
160
+ [rel_point[0] + width, rel_point[1] - height - @border_width / 2.0] )
161
+ end
162
+
163
+ if borders.include?(:top)
164
+ @document.stroke_line(
165
+ [ rel_point[0] + @border_width / 2.0, rel_point[1] ],
166
+ [ rel_point[0] - @border_width / 2.0 + width, rel_point[1] ])
167
+ end
168
+
169
+ if borders.include?(:bottom)
170
+ @document.stroke_line [rel_point[0], rel_point[1] - height ],
171
+ [rel_point[0] + width, rel_point[1] - height]
172
+ end
173
+ end
174
+
175
+ end
176
+
177
+ borders
178
+
179
+ end
180
+
181
+ @document.bounding_box( [@point[0] + @horizontal_padding,
182
+ @point[1] - @vertical_padding],
183
+ :width => text_area_width,
184
+ :height => height - @vertical_padding) do
185
+ @document.move_down((@document.font.line_gap - @document.font.descender)/2)
186
+
187
+ options = {:align => @align, :final_gap => false}
188
+
189
+ options[:size] = @font_size if @font_size
190
+ options[:style] = @font_style if @font_style
191
+
192
+ @document.mask(:fill_color) do
193
+ @document.fill_color @text_color if @text_color
194
+ @document.text @text, options
195
+ end
196
+ end
197
+ end
198
+
199
+ private
200
+
201
+ def borders
202
+ @borders ||= case @border_style
203
+ when :all
204
+ [:top,:left,:right,:bottom]
205
+ when :sides
206
+ [:left,:right]
207
+ when :no_top
208
+ [:left,:right,:bottom]
209
+ when :no_bottom
210
+ [:left,:right,:top]
211
+ when :bottom_only
212
+ [:bottom]
213
+ when :none
214
+ []
215
+ end
216
+ end
217
+
218
+ end
219
+
220
+ class CellFake < Cell #:nodoc:
221
+ def height
222
+ 0
223
+ end
224
+
225
+ def draw
226
+ end
227
+ end
228
+
229
+ class CellBlock #:nodoc:
230
+
231
+ # Not sure if this class is something I want to expose in the public API.
232
+
233
+ def initialize(document)
234
+ @document = document
235
+ @cells = []
236
+ @width = 0
237
+ @height = 0
238
+ end
239
+
240
+ attr_reader :width, :height, :cells
241
+ attr_accessor :background_color, :text_color, :border_color
242
+
243
+ def <<(cell)
244
+ @cells << cell
245
+ @height = cell.height if cell.height > @height
246
+ @width += cell.width
247
+ self
248
+ end
249
+
250
+ def draw
251
+ y = @document.y
252
+ x = @document.bounds.absolute_left
253
+
254
+ @cells.each do |e|
255
+ e.point = [x - @document.bounds.absolute_left,
256
+ y - @document.bounds.absolute_bottom]
257
+ e.height ||= @height
258
+ e.background_color ||= @background_color
259
+ e.text_color ||= @text_color
260
+ e.border_color ||= @border_color
261
+ e.draw
262
+ x += e.width
263
+ end
264
+
265
+ @document.y = y - @height
266
+ end
267
+
268
+ def border_width
269
+ @cells[0].border_width
270
+ end
271
+
272
+ def border_style=(s)
273
+ @cells.each { |e| e.border_style = s }
274
+ end
275
+
276
+ def align=(align)
277
+ @cells.each { |e| e.align = align }
278
+ end
279
+
280
+ def border_style
281
+ @cells[0].border_style
282
+ end
283
+
284
+ end
285
+ end
286
+
287
+ end