prawn 0.1.0

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.
Files changed (120) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +30 -0
  4. data/Rakefile +83 -0
  5. data/data/fonts/Activa.ttf +0 -0
  6. data/data/fonts/Chalkboard.ttf +0 -0
  7. data/data/fonts/Courier-Bold.afm +342 -0
  8. data/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/data/fonts/Courier-Oblique.afm +342 -0
  10. data/data/fonts/Courier.afm +342 -0
  11. data/data/fonts/DejaVuSans.ttf +0 -0
  12. data/data/fonts/Dustismo_Roman.ttf +0 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/data/fonts/comicsans.ttf +0 -0
  25. data/data/fonts/gkai00mp.ttf +0 -0
  26. data/data/images/dice.png +0 -0
  27. data/data/images/pigs.jpg +0 -0
  28. data/data/images/ruport.png +0 -0
  29. data/data/images/ruport_data.dat +0 -0
  30. data/data/images/ruport_transparent.png +0 -0
  31. data/data/images/stef.jpg +0 -0
  32. data/data/shift_jis_text.txt +1 -0
  33. data/examples/addressbook.csv +6 -0
  34. data/examples/alignment.rb +16 -0
  35. data/examples/bounding_boxes.pdf +62 -0
  36. data/examples/bounding_boxes.rb +30 -0
  37. data/examples/canvas.pdf +81 -0
  38. data/examples/canvas.rb +12 -0
  39. data/examples/cell.rb +27 -0
  40. data/examples/currency.csv +1834 -0
  41. data/examples/curves.rb +10 -0
  42. data/examples/fancy_table.rb +48 -0
  43. data/examples/font_size.rb +19 -0
  44. data/examples/hexagon.rb +14 -0
  45. data/examples/image.pdf +0 -0
  46. data/examples/image.rb +23 -0
  47. data/examples/image2.rb +13 -0
  48. data/examples/inline_styles.pdf +117 -0
  49. data/examples/kerning.rb +27 -0
  50. data/examples/line.rb +31 -0
  51. data/examples/multi_page_layout.rb +14 -0
  52. data/examples/on_page_start.rb +17 -0
  53. data/examples/page_geometry.rb +28 -0
  54. data/examples/polygons.rb +16 -0
  55. data/examples/ruport_formatter.rb +47 -0
  56. data/examples/ruport_helpers.rb +17 -0
  57. data/examples/russian_boxes.rb +34 -0
  58. data/examples/simple_text.rb +15 -0
  59. data/examples/simple_text_ttf.rb +16 -0
  60. data/examples/sjis.rb +19 -0
  61. data/examples/table.rb +45 -0
  62. data/examples/table_bench.rb +92 -0
  63. data/examples/text_flow.rb +65 -0
  64. data/examples/utf8.rb +12 -0
  65. data/lib/prawn.rb +33 -0
  66. data/lib/prawn/compatibility.rb +33 -0
  67. data/lib/prawn/document.rb +334 -0
  68. data/lib/prawn/document/bounding_box.rb +253 -0
  69. data/lib/prawn/document/page_geometry.rb +78 -0
  70. data/lib/prawn/document/table.rb +253 -0
  71. data/lib/prawn/document/text.rb +346 -0
  72. data/lib/prawn/errors.rb +33 -0
  73. data/lib/prawn/font.rb +5 -0
  74. data/lib/prawn/font/cmap.rb +59 -0
  75. data/lib/prawn/font/metrics.rb +414 -0
  76. data/lib/prawn/font/wrapping.rb +45 -0
  77. data/lib/prawn/graphics.rb +285 -0
  78. data/lib/prawn/graphics/cell.rb +226 -0
  79. data/lib/prawn/images.rb +241 -0
  80. data/lib/prawn/images/jpg.rb +43 -0
  81. data/lib/prawn/images/png.rb +178 -0
  82. data/lib/prawn/pdf_object.rb +64 -0
  83. data/lib/prawn/reference.rb +47 -0
  84. data/spec/bounding_box_spec.rb +120 -0
  85. data/spec/box_calculation_spec.rb +17 -0
  86. data/spec/document_spec.rb +152 -0
  87. data/spec/graphics_spec.rb +250 -0
  88. data/spec/images_spec.rb +42 -0
  89. data/spec/jpg_spec.rb +25 -0
  90. data/spec/metrics_spec.rb +60 -0
  91. data/spec/pdf_object_spec.rb +102 -0
  92. data/spec/png_spec.rb +35 -0
  93. data/spec/reference_spec.rb +29 -0
  94. data/spec/spec_helper.rb +29 -0
  95. data/spec/table_spec.rb +145 -0
  96. data/spec/text_spec.rb +190 -0
  97. data/vendor/font_ttf/ttf.rb +20 -0
  98. data/vendor/font_ttf/ttf/datatypes.rb +189 -0
  99. data/vendor/font_ttf/ttf/encodings.rb +140 -0
  100. data/vendor/font_ttf/ttf/exceptions.rb +28 -0
  101. data/vendor/font_ttf/ttf/file.rb +290 -0
  102. data/vendor/font_ttf/ttf/fontchunk.rb +77 -0
  103. data/vendor/font_ttf/ttf/table/cmap.rb +408 -0
  104. data/vendor/font_ttf/ttf/table/cvt.rb +49 -0
  105. data/vendor/font_ttf/ttf/table/fpgm.rb +48 -0
  106. data/vendor/font_ttf/ttf/table/gasp.rb +88 -0
  107. data/vendor/font_ttf/ttf/table/glyf.rb +452 -0
  108. data/vendor/font_ttf/ttf/table/head.rb +86 -0
  109. data/vendor/font_ttf/ttf/table/hhea.rb +96 -0
  110. data/vendor/font_ttf/ttf/table/hmtx.rb +98 -0
  111. data/vendor/font_ttf/ttf/table/kern.rb +186 -0
  112. data/vendor/font_ttf/ttf/table/loca.rb +75 -0
  113. data/vendor/font_ttf/ttf/table/maxp.rb +81 -0
  114. data/vendor/font_ttf/ttf/table/name.rb +222 -0
  115. data/vendor/font_ttf/ttf/table/os2.rb +172 -0
  116. data/vendor/font_ttf/ttf/table/post.rb +120 -0
  117. data/vendor/font_ttf/ttf/table/prep.rb +27 -0
  118. data/vendor/font_ttf/ttf/table/vhea.rb +45 -0
  119. data/vendor/font_ttf/ttf/table/vmtx.rb +36 -0
  120. metadata +180 -0
@@ -0,0 +1,253 @@
1
+ # encoding: utf-8
2
+
3
+ module Prawn
4
+ class Document
5
+
6
+ # A bounding box serves two important purposes:
7
+ # * Provide bounds for flowing text, starting at a given point
8
+ # * Translate the origin (0,0) for graphics primitives, for the purposes
9
+ # of simplifying coordinate math.
10
+ #
11
+ # When flowing text, the usage of a bounding box is simple. Text will
12
+ # begin at the point specified, flowing the width of the bounding box.
13
+ # After the block exits, the text drawing position will be moved to
14
+ # the bottom of the bounding box (y - height). Currently, Prawn allows
15
+ # text to overflow the bottom border of the bounding box, so it is up to
16
+ # the user to ensure the text provided will fit within the height of the
17
+ # bounding box.
18
+ #
19
+ # pdf.bounding_box([100,500], :width => 100, :height => 300) do
20
+ # pdf.text "This text will flow in a very narrow box starting" +
21
+ # "from [100,500]. The pointer will then be moved to [100,200]" +
22
+ # "and return to the margin_box"
23
+ # end
24
+ #
25
+ # When translating coordinates, the idea is to allow the user to draw
26
+ # relative to the origin, and then translate their drawing to a specified
27
+ # area of the document, rather than adjust all their drawing coordinates
28
+ # to match this new region.
29
+ #
30
+ # Take for example two triangles which share one point, drawn from the
31
+ # origin:
32
+ #
33
+ # pdf.polygon [0,250], [0,0], [150,100]
34
+ # pdf.polygon [100,0], [150,100], [200,0]
35
+ #
36
+ # It would be easy enough to translate these triangles to another point,
37
+ # e.g [200,200]
38
+ #
39
+ # pdf.polygon [200,450], [200,200], [350,300]
40
+ # pdf.polygon [300,200], [350,300], [400,200]
41
+ #
42
+ # However, each time you want to move the drawing, you'd need to alter
43
+ # every point in the drawing calls, which as you might imagine, can become
44
+ # tedious.
45
+ #
46
+ # If instead, we think of the drawing as being bounded by a box, we can
47
+ # see that the image is 200 points wide by 250 points tall.
48
+ #
49
+ # To translate it to a new origin, we simply select a point at (x,y+height)
50
+ #
51
+ # Using the [200,200] example:
52
+ #
53
+ # pdf.bounding_box([200,450], :width => 200, :height => 250) do
54
+ # pdf.polygon [0,250], [0,0], [150,100]
55
+ # pdf.polygon [100,0], [150,100], [200,0]
56
+ # end
57
+ #
58
+ # Notice that the drawing is still relative to the origin. If we want to
59
+ # move this drawing around the document, we simply need to recalculate the
60
+ # top-left corner of the rectangular bounding-box, and all of our graphics
61
+ # calls remain unmodified.
62
+ #
63
+ # By default, bounding boxes are specified relative to the document's
64
+ # margin_box (which is itself a bounding box). You can also nest bounding
65
+ # boxes, allowing you to build components which are relative to each other
66
+ #
67
+ # pdf.bouding_box([200,450], :width => 200, :height => 250) do
68
+ # pdf.bounding_box([50,200], :width => 50, :height => 50) do
69
+ # # a 50x50 bounding box that starts 50 pixels left and 50 pixels down
70
+ # # the parent bounding box.
71
+ # end
72
+ # end
73
+ #
74
+ # If you wish to position the bounding boxes at absolute coordinates rather
75
+ # than relative to the margins or other bounding boxes, you can use canvas()
76
+ #
77
+ # pdf.canvas do
78
+ # pdf.bounding_box([200,450], :width => 200, :height => 250) do
79
+ # # positioned at 'real' (200,450)
80
+ # end
81
+ # end
82
+ #
83
+ # Of course, if you use canvas, you will be responsible for ensuring that
84
+ # you remain within the printable area of your document.
85
+ #
86
+ def bounding_box(*args, &block)
87
+ init_bounding_box(block) do |parent_box|
88
+ # Offset to relative positions
89
+ top_left = args[0]
90
+ top_left[0] += parent_box.absolute_left
91
+ top_left[1] += parent_box.absolute_bottom
92
+
93
+ @bounding_box = BoundingBox.new(self, *args)
94
+ end
95
+ end
96
+
97
+ # A shortcut to produce a bounding box which is mapped to the document's
98
+ # absolute coordinates, regardless of how things are nested or margin sizes.
99
+ #
100
+ # pdf.canvas do
101
+ # pdf.line pdf.bounds.bottom_left, pdf.bounds.top_right
102
+ # end
103
+ #
104
+ def canvas(&block)
105
+ init_bounding_box(block) do |_|
106
+ @bounding_box = BoundingBox.new(self, [0,page_dimensions[3]],
107
+ :width => page_dimensions[2],
108
+ :height => page_dimensions[3]
109
+ )
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def init_bounding_box(user_block, &init_block)
116
+ parent_box = @bounding_box
117
+
118
+ init_block.call(parent_box)
119
+
120
+ self.y = @bounding_box.absolute_top
121
+ user_block.call
122
+ self.y = @bounding_box.absolute_bottom
123
+
124
+ @bounding_box = parent_box
125
+ end
126
+
127
+ class BoundingBox
128
+
129
+ def initialize(parent, point, options={}) #:nodoc:
130
+ @parent = parent
131
+ @x, @y = point
132
+ @width, @height = options[:width], options[:height]
133
+ end
134
+
135
+ # The translated origin (x,y-height) which describes the location
136
+ # of the bottom left corner of the bounding box
137
+ #
138
+ def anchor
139
+ [@x, @y - height]
140
+ end
141
+
142
+ # Relative left x-coordinate of the bounding box. (Always 0)
143
+ #
144
+ def left
145
+ 0
146
+ end
147
+
148
+ # Relative right x-coordinate of the bounding box. (Equal to the box width)
149
+ #
150
+ def right
151
+ @width
152
+ end
153
+
154
+ # Relative top y-coordinate of the bounding box. (Equal to the box height)
155
+ #
156
+ def top
157
+ height
158
+ end
159
+
160
+ # Relative bottom y-coordinate of the bounding box (Always 0)
161
+ #
162
+ def bottom
163
+ 0
164
+ end
165
+
166
+ # Relative top-left point of the bounding_box
167
+ #
168
+ def top_left
169
+ [left,top]
170
+ end
171
+
172
+ # Relative top-right point of the bounding box
173
+ #
174
+ def top_right
175
+ [right,top]
176
+ end
177
+
178
+ # Relative bottom-right point of the bounding box
179
+ #
180
+ def bottom_right
181
+ [right,bottom]
182
+ end
183
+
184
+ # Relative bottom-left point of the bounding box
185
+ #
186
+ def bottom_left
187
+ [left,bottom]
188
+ end
189
+
190
+ # Absolute left x-coordinate of the bounding box
191
+ #
192
+ def absolute_left
193
+ @x
194
+ end
195
+
196
+ # Absolute right x-coordinate of the bounding box
197
+ #
198
+ def absolute_right
199
+ @x + width
200
+ end
201
+
202
+ # Absolute top y-coordinate of the bounding box
203
+ #
204
+ def absolute_top
205
+ @y
206
+ end
207
+
208
+ # Absolute bottom y-coordinate of the bottom box
209
+ #
210
+ def absolute_bottom
211
+ @y - height
212
+ end
213
+
214
+ # Absolute top-left point of the bounding box
215
+ #
216
+ def absolute_top_left
217
+ [absolute_left, absolute_top]
218
+ end
219
+
220
+ # Absolute top-right point of the bounding box
221
+ #
222
+ def absolute_top_right
223
+ [absolute_right, absolute_top]
224
+ end
225
+
226
+ # Absolute bottom-left point of the bounding box
227
+ #
228
+ def absolute_bottom_left
229
+ [absolute_left, absolute_bottom]
230
+ end
231
+
232
+ # Absolute bottom-left point of the bounding box
233
+ #
234
+ def absolute_bottom_right
235
+ [absolute_right, absolute_bottom]
236
+ end
237
+
238
+ # Width of the bounding box
239
+ #
240
+ def width
241
+ @width
242
+ end
243
+
244
+ # Height of the bounding box. If the box is 'stretchy' (unspecified
245
+ # height attribute), height is calculated as the distance from the top of
246
+ # the box to the current drawing position.
247
+ #
248
+ def height
249
+ @height || absolute_top - @parent.y
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ # page_geometry.rb : Describes PDF page geometries
4
+ #
5
+ # Copyright April 2008, 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
+ module PageGeometry
12
+
13
+ # Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
14
+ SIZES = { "4A0" => [4767.87, 6740.79],
15
+ "2A0" => [3370.39, 4767.87],
16
+ "A0" => [2383.94, 3370.39],
17
+ "A1" => [1683.78, 2383.94],
18
+ "A2" => [1190.55, 1683.78],
19
+ "A3" => [841.89, 1190.55],
20
+ "A4" => [595.28, 841.89],
21
+ "A5" => [419.53, 595.28],
22
+ "A6" => [297.64, 419.53],
23
+ "A7" => [209.76, 297.64],
24
+ "A8" => [147.40, 209.76],
25
+ "A9" => [104.88, 147.40],
26
+ "A10" => [73.70, 104.88],
27
+ "B0" => [2834.65, 4008.19],
28
+ "B1" => [2004.09, 2834.65],
29
+ "B2" => [1417.32, 2004.09],
30
+ "B3" => [1000.63, 1417.32],
31
+ "B4" => [708.66, 1000.63],
32
+ "B5" => [498.90, 708.66],
33
+ "B6" => [354.33, 498.90],
34
+ "B7" => [249.45, 354.33],
35
+ "B8" => [175.75, 249.45],
36
+ "B9" => [124.72, 175.75],
37
+ "B10" => [87.87, 124.72],
38
+ "C0" => [2599.37, 3676.54],
39
+ "C1" => [1836.85, 2599.37],
40
+ "C2" => [1298.27, 1836.85],
41
+ "C3" => [918.43, 1298.27],
42
+ "C4" => [649.13, 918.43],
43
+ "C5" => [459.21, 649.13],
44
+ "C6" => [323.15, 459.21],
45
+ "C7" => [229.61, 323.15],
46
+ "C8" => [161.57, 229.61],
47
+ "C9" => [113.39, 161.57],
48
+ "C10" => [79.37, 113.39],
49
+ "RA0" => [2437.80, 3458.27],
50
+ "RA1" => [1729.13, 2437.80],
51
+ "RA2" => [1218.90, 1729.13],
52
+ "RA3" => [864.57, 1218.90],
53
+ "RA4" => [609.45, 864.57],
54
+ "SRA0" => [2551.18, 3628.35],
55
+ "SRA1" => [1814.17, 2551.18],
56
+ "SRA2" => [1275.59, 1814.17],
57
+ "SRA3" => [907.09, 1275.59],
58
+ "SRA4" => [637.80, 907.09],
59
+ "LETTER" => [612.00, 792.00],
60
+ "LEGAL" => [612.00, 1008.00],
61
+ "FOLIO" => [612.00, 936.00],
62
+ "EXECUTIVE" => [521.86, 756.00] }
63
+
64
+ def page_dimensions #:nodoc:
65
+ coords = SIZES[page_size] || page_size
66
+ [0,0] + case(page_layout)
67
+ when :portrait
68
+ coords
69
+ when :landscape
70
+ coords.reverse
71
+ else
72
+ raise Prawn::Errors::InvalidPageLayout,
73
+ "Layout must be either :portrait or :landscape"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,253 @@
1
+ # encoding: utf-8
2
+
3
+ module Prawn
4
+ class Document
5
+
6
+ # Builds and renders a Document::Table object from raw data.
7
+ # For details on the options that can be passed, see
8
+ # Document::Table.new
9
+ #
10
+ # data = [["Gregory","Brown"],["James","Healy"],["Jia","Wu"]]
11
+ #
12
+ # Prawn::Document.generate("table.pdf") do
13
+ #
14
+ # # Default table, without headers
15
+ # table(data)
16
+ #
17
+ # # Default table with headers
18
+ # table data, :headers => ["First Name", "Last Name"]
19
+ #
20
+ # # Very close to PDF::Writer's default SimpleTable output
21
+ # table data, :headers => ["First Name", "Last Name"],
22
+ # :font_size => 10,
23
+ # :vertical_padding => 2,
24
+ # :horizontal_padding => 5,
25
+ # :position => :center,
26
+ # :row_colors => :pdf_writer,
27
+ #
28
+ # # Grid border style with explicit column widths.
29
+ # table data, :border_style => :grid,
30
+ # :widths => { 0 => 100, 1 => 150 }
31
+ #
32
+ # end
33
+ #
34
+ def table(data,options={})
35
+ Prawn::Document::Table.new(data,self,options).draw
36
+ end
37
+
38
+ # This class implements simple PDF table generation.
39
+ #
40
+ # Prawn tables have the following features:
41
+ #
42
+ # * Can be generated with or without headers
43
+ # * Can tweak horizontal and vertical padding of text
44
+ # * Minimal styling support (borders / row background colors)
45
+ # * Can be positioned by bounding boxes (left/center aligned) or an
46
+ # absolute x position
47
+ # * Automated page-breaking as needed
48
+ # * Column widths can be calculated automatically or defined explictly on a
49
+ # column by column basis
50
+ #
51
+ # The current implementation is a bit barebones, but covers most of the
52
+ # basic needs for PDF table generation. If you have feature requests,
53
+ # please share them at: http://groups.google.com/group/prawn-ruby
54
+ #
55
+ # Tables will be revisited before the end of the Ruby Mendicant project and
56
+ # the most commonly needed functionality will likely be added.
57
+ #
58
+ class Table
59
+
60
+ attr_reader :col_widths # :nodoc:
61
+
62
+ # Creates a new Document::Table object. This is generally called
63
+ # indirectly through Document#table but can also be used explictly.
64
+ #
65
+ # The <tt>data</tt> argument is a two dimensional array of strings,
66
+ # organized by row, e.g. [["r1-col1","r1-col2"],["r2-col2","r2-col2"]].
67
+ # As with all Prawn text drawing operations, strings must be UTF-8 encoded.
68
+ #
69
+ # The following options are available for customizing your tables, with
70
+ # defaults shown in [] at the end of each description.
71
+ #
72
+ # <tt>:font_size</tt>:: The font size for the text cells . [12]
73
+ # <tt>:horizontal_padding</tt>:: The horizontal cell padding in PDF points [5]
74
+ # <tt>:vertical_padding</tt>:: The vertical cell padding in PDF points [5]
75
+ # <tt>:padding</tt>:: Horizontal and vertical cell padding (overrides both)
76
+ # <tt>:border</tt>:: With of border lines in PDF points [1]
77
+ # <tt>:border_style</tt>:: If set to :grid, fills in all borders. Otherwise, borders are drawn on columns only, not rows
78
+ # <tt>:position</tt>:: One of <tt>:left</tt>, <tt>:center</tt> or <tt>n</tt>, where <tt>n</tt> is an x-offset from the left edge of the current bounding box
79
+ # <tt>:widths:</tt> A hash of indices and widths in PDF points. E.g. <tt>{ 0 => 50, 1 => 100 }</tt>
80
+ # <tt>:row_colors</tt>:: An array of row background colors which are used cyclicly.
81
+ # <tt>:align</tt>:: Alignment of text in columns [:left]
82
+ #
83
+ # Row colors are specified as html encoded values, e.g.
84
+ # ["ffffff","aaaaaa","ccaaff"]. You can also specify
85
+ # <tt>:row_colors => :pdf_writer</tt> if you wish to use the default color
86
+ # scheme from the PDF::Writer library.
87
+ #
88
+ # See Document#table for typical usage, as directly using this class is
89
+ # not recommended unless you know why you want to do it.
90
+ #
91
+ def initialize(data, document,options={})
92
+ @data = data
93
+ @document = document
94
+ @font_size = options[:font_size] || 12
95
+ @border_style = options[:border_style]
96
+ @border = options[:border] || 1
97
+ @position = options[:position] || :left
98
+ @headers = options[:headers]
99
+ @row_colors = options[:row_colors]
100
+ @align = options[:align]
101
+
102
+ @horizontal_padding = options[:horizontal_padding] || 5
103
+ @vertical_padding = options[:vertical_padding] || 5
104
+
105
+ if options[:padding]
106
+ @horizontal_padding = @vertical_padding = options[:padding]
107
+ end
108
+
109
+
110
+ @row_colors = ["ffffff","cccccc"] if @row_colors == :pdf_writer
111
+
112
+ @original_row_colors = @row_colors.dup if @row_colors
113
+
114
+ calculate_column_widths(options[:widths])
115
+ end
116
+
117
+ # Width of the table in PDF points
118
+ #
119
+ def width
120
+ @col_widths.inject(0) { |s,r| s + r }
121
+ end
122
+
123
+ # Draws the table onto the PDF document
124
+ #
125
+ def draw
126
+ case(@position)
127
+ when :center
128
+ x = (@document.bounds.width - width) / 2.0
129
+ y = @document.y - @document.bounds.absolute_bottom
130
+ @document.bounding_box [x, y], :width => width do
131
+ generate_table
132
+ end
133
+ when Numeric
134
+ x = @position
135
+ y = @document.y - @document.bounds.absolute_bottom
136
+ @document.bounding_box [x,y], :width => width do
137
+ generate_table
138
+ end
139
+ else
140
+ generate_table
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def calculate_column_widths(manual_widths=nil)
147
+ @col_widths = [0] * @data[0].length
148
+ renderable_data.each do |row|
149
+ row.each_with_index do |cell,i|
150
+ length = cell.to_s.lines.map { |e|
151
+ @document.font_metrics.string_width(e,@font_size) }.max.to_f +
152
+ 2*@horizontal_padding
153
+ @col_widths[i] = length if length > @col_widths[i]
154
+ end
155
+ end
156
+
157
+ # TODO: Could optimize here
158
+ manual_widths.each { |k,v| @col_widths[k] = v } if manual_widths
159
+ end
160
+
161
+ def renderable_data
162
+ if @headers
163
+ [@headers] + @data
164
+ else
165
+ @data
166
+ end
167
+ end
168
+
169
+ def generate_table
170
+ page_contents = []
171
+ y_pos = @document.y
172
+
173
+ @document.font_size(@font_size) do
174
+ renderable_data.each_with_index do |row,index|
175
+ c = Prawn::Graphics::CellBlock.new(@document)
176
+ row.each_with_index do |e,i|
177
+ case(e)
178
+ when Prawn::Graphics::Cell
179
+ e.document = @document
180
+ e.width = @col_widths[i]
181
+ e.horizontal_padding = @horizontal_padding
182
+ e.vertical_padding = @vertical_padding
183
+ e.border = @border
184
+ e.border_style = :sides
185
+ e.align = @align
186
+ c << e
187
+ else
188
+ c << Prawn::Graphics::Cell.new(
189
+ :document => @document,
190
+ :text => e.to_s,
191
+ :width => @col_widths[i],
192
+ :horizontal_padding => @horizontal_padding,
193
+ :vertical_padding => @vertical_padding,
194
+ :border => @border,
195
+ :border_style => :sides,
196
+ :align => @align )
197
+ end
198
+ end
199
+
200
+ if c.height > y_pos - @document.margin_box.absolute_bottom
201
+ draw_page(page_contents)
202
+ @document.start_new_page
203
+ if @headers
204
+ page_contents = [page_contents[0]]
205
+ y_pos = @document.y - page_contents[0].height
206
+ else
207
+ page_contents = []
208
+ y_pos = @document.y
209
+ end
210
+ end
211
+
212
+ page_contents << c
213
+
214
+ y_pos -= c.height
215
+
216
+ if index == renderable_data.length - 1
217
+ draw_page(page_contents)
218
+ end
219
+
220
+ end
221
+ @document.y -= @vertical_padding
222
+ end
223
+ end
224
+
225
+ def draw_page(contents)
226
+ return if contents.empty?
227
+
228
+ if @border_style == :grid || contents.length == 1
229
+ contents.each { |e| e.border_style = :all }
230
+ else
231
+ contents.first.border_style = @headers ? :all : :no_bottom
232
+ contents.last.border_style = :no_top
233
+ end
234
+
235
+ contents.each do |x|
236
+ x.background_color = next_row_color if @row_colors
237
+ x.draw
238
+ end
239
+
240
+ reset_row_colors
241
+ end
242
+
243
+ def next_row_color
244
+ @row_colors.unshift(@row_colors.pop).last
245
+ end
246
+
247
+ def reset_row_colors
248
+ @row_colors = @original_row_colors.dup if @row_colors
249
+ end
250
+
251
+ end
252
+ end
253
+ end