jerryvos-prawn-layout 0.2.0.3 → 0.2.0.4

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