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.
- data/COPYING +340 -0
- data/LICENSE +56 -0
- data/README +30 -0
- data/Rakefile +83 -0
- data/data/fonts/Activa.ttf +0 -0
- data/data/fonts/Chalkboard.ttf +0 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/data/fonts/Dustismo_Roman.ttf +0 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/data/fonts/comicsans.ttf +0 -0
- data/data/fonts/gkai00mp.ttf +0 -0
- data/data/images/dice.png +0 -0
- data/data/images/pigs.jpg +0 -0
- data/data/images/ruport.png +0 -0
- data/data/images/ruport_data.dat +0 -0
- data/data/images/ruport_transparent.png +0 -0
- data/data/images/stef.jpg +0 -0
- data/data/shift_jis_text.txt +1 -0
- data/examples/addressbook.csv +6 -0
- data/examples/alignment.rb +16 -0
- data/examples/bounding_boxes.pdf +62 -0
- data/examples/bounding_boxes.rb +30 -0
- data/examples/canvas.pdf +81 -0
- data/examples/canvas.rb +12 -0
- data/examples/cell.rb +27 -0
- data/examples/currency.csv +1834 -0
- data/examples/curves.rb +10 -0
- data/examples/fancy_table.rb +48 -0
- data/examples/font_size.rb +19 -0
- data/examples/hexagon.rb +14 -0
- data/examples/image.pdf +0 -0
- data/examples/image.rb +23 -0
- data/examples/image2.rb +13 -0
- data/examples/inline_styles.pdf +117 -0
- data/examples/kerning.rb +27 -0
- data/examples/line.rb +31 -0
- data/examples/multi_page_layout.rb +14 -0
- data/examples/on_page_start.rb +17 -0
- data/examples/page_geometry.rb +28 -0
- data/examples/polygons.rb +16 -0
- data/examples/ruport_formatter.rb +47 -0
- data/examples/ruport_helpers.rb +17 -0
- data/examples/russian_boxes.rb +34 -0
- data/examples/simple_text.rb +15 -0
- data/examples/simple_text_ttf.rb +16 -0
- data/examples/sjis.rb +19 -0
- data/examples/table.rb +45 -0
- data/examples/table_bench.rb +92 -0
- data/examples/text_flow.rb +65 -0
- data/examples/utf8.rb +12 -0
- data/lib/prawn.rb +33 -0
- data/lib/prawn/compatibility.rb +33 -0
- data/lib/prawn/document.rb +334 -0
- data/lib/prawn/document/bounding_box.rb +253 -0
- data/lib/prawn/document/page_geometry.rb +78 -0
- data/lib/prawn/document/table.rb +253 -0
- data/lib/prawn/document/text.rb +346 -0
- data/lib/prawn/errors.rb +33 -0
- data/lib/prawn/font.rb +5 -0
- data/lib/prawn/font/cmap.rb +59 -0
- data/lib/prawn/font/metrics.rb +414 -0
- data/lib/prawn/font/wrapping.rb +45 -0
- data/lib/prawn/graphics.rb +285 -0
- data/lib/prawn/graphics/cell.rb +226 -0
- data/lib/prawn/images.rb +241 -0
- data/lib/prawn/images/jpg.rb +43 -0
- data/lib/prawn/images/png.rb +178 -0
- data/lib/prawn/pdf_object.rb +64 -0
- data/lib/prawn/reference.rb +47 -0
- data/spec/bounding_box_spec.rb +120 -0
- data/spec/box_calculation_spec.rb +17 -0
- data/spec/document_spec.rb +152 -0
- data/spec/graphics_spec.rb +250 -0
- data/spec/images_spec.rb +42 -0
- data/spec/jpg_spec.rb +25 -0
- data/spec/metrics_spec.rb +60 -0
- data/spec/pdf_object_spec.rb +102 -0
- data/spec/png_spec.rb +35 -0
- data/spec/reference_spec.rb +29 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/table_spec.rb +145 -0
- data/spec/text_spec.rb +190 -0
- data/vendor/font_ttf/ttf.rb +20 -0
- data/vendor/font_ttf/ttf/datatypes.rb +189 -0
- data/vendor/font_ttf/ttf/encodings.rb +140 -0
- data/vendor/font_ttf/ttf/exceptions.rb +28 -0
- data/vendor/font_ttf/ttf/file.rb +290 -0
- data/vendor/font_ttf/ttf/fontchunk.rb +77 -0
- data/vendor/font_ttf/ttf/table/cmap.rb +408 -0
- data/vendor/font_ttf/ttf/table/cvt.rb +49 -0
- data/vendor/font_ttf/ttf/table/fpgm.rb +48 -0
- data/vendor/font_ttf/ttf/table/gasp.rb +88 -0
- data/vendor/font_ttf/ttf/table/glyf.rb +452 -0
- data/vendor/font_ttf/ttf/table/head.rb +86 -0
- data/vendor/font_ttf/ttf/table/hhea.rb +96 -0
- data/vendor/font_ttf/ttf/table/hmtx.rb +98 -0
- data/vendor/font_ttf/ttf/table/kern.rb +186 -0
- data/vendor/font_ttf/ttf/table/loca.rb +75 -0
- data/vendor/font_ttf/ttf/table/maxp.rb +81 -0
- data/vendor/font_ttf/ttf/table/name.rb +222 -0
- data/vendor/font_ttf/ttf/table/os2.rb +172 -0
- data/vendor/font_ttf/ttf/table/post.rb +120 -0
- data/vendor/font_ttf/ttf/table/prep.rb +27 -0
- data/vendor/font_ttf/ttf/table/vhea.rb +45 -0
- data/vendor/font_ttf/ttf/table/vmtx.rb +36 -0
- 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
|