pdf-writer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +44 -0
- data/LICENCE +118 -0
- data/README +32 -0
- data/bin/loader +54 -0
- data/bin/manual +22 -0
- data/bin/manual.bat +2 -0
- data/demo/chunkybacon.rb +28 -0
- data/demo/code.rb +63 -0
- data/demo/colornames.rb +843 -0
- data/demo/demo.rb +65 -0
- data/demo/gettysburg.rb +58 -0
- data/demo/hello.rb +18 -0
- data/demo/individual-i.rb +81 -0
- data/demo/pac.rb +62 -0
- data/demo/pagenumber.rb +67 -0
- data/demo/qr-language.rb +573 -0
- data/demo/qr-library.rb +371 -0
- data/images/chunkybacon.jpg +0 -0
- data/images/chunkybacon.png +0 -0
- data/images/pdfwriter-icon.jpg +0 -0
- data/images/pdfwriter-small.jpg +0 -0
- data/lib/pdf/charts.rb +13 -0
- data/lib/pdf/charts/stddev.rb +431 -0
- data/lib/pdf/grid.rb +135 -0
- data/lib/pdf/math.rb +108 -0
- data/lib/pdf/quickref.rb +330 -0
- data/lib/pdf/simpletable.rb +946 -0
- data/lib/pdf/techbook.rb +890 -0
- data/lib/pdf/writer.rb +2661 -0
- data/lib/pdf/writer/arc4.rb +63 -0
- data/lib/pdf/writer/fontmetrics.rb +201 -0
- data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
- data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
- data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
- data/lib/pdf/writer/fonts/Courier.afm +342 -0
- data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
- data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
- data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
- data/lib/pdf/writer/fonts/MustRead.html +1 -0
- data/lib/pdf/writer/fonts/Symbol.afm +213 -0
- data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
- data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
- data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
- data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
- data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
- data/lib/pdf/writer/graphics.rb +727 -0
- data/lib/pdf/writer/graphics/imageinfo.rb +365 -0
- data/lib/pdf/writer/lang.rb +43 -0
- data/lib/pdf/writer/lang/en.rb +77 -0
- data/lib/pdf/writer/object.rb +23 -0
- data/lib/pdf/writer/object/action.rb +40 -0
- data/lib/pdf/writer/object/annotation.rb +42 -0
- data/lib/pdf/writer/object/catalog.rb +39 -0
- data/lib/pdf/writer/object/contents.rb +68 -0
- data/lib/pdf/writer/object/destination.rb +40 -0
- data/lib/pdf/writer/object/encryption.rb +53 -0
- data/lib/pdf/writer/object/font.rb +76 -0
- data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
- data/lib/pdf/writer/object/fontencoding.rb +39 -0
- data/lib/pdf/writer/object/image.rb +168 -0
- data/lib/pdf/writer/object/info.rb +55 -0
- data/lib/pdf/writer/object/outline.rb +30 -0
- data/lib/pdf/writer/object/outlines.rb +30 -0
- data/lib/pdf/writer/object/page.rb +195 -0
- data/lib/pdf/writer/object/pages.rb +115 -0
- data/lib/pdf/writer/object/procset.rb +46 -0
- data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
- data/lib/pdf/writer/ohash.rb +58 -0
- data/lib/pdf/writer/oreader.rb +25 -0
- data/lib/pdf/writer/state.rb +48 -0
- data/lib/pdf/writer/strokestyle.rb +138 -0
- data/manual.pwd +5151 -0
- metadata +147 -0
@@ -0,0 +1,946 @@
|
|
1
|
+
#--
|
2
|
+
# PDF::Writer for Ruby.
|
3
|
+
# http://rubyforge.org/projects/ruby-pdf/
|
4
|
+
# Copyright 2003 - 2005 Austin Ziegler.
|
5
|
+
#
|
6
|
+
# Licensed under a MIT-style licence. See LICENCE in the main distribution
|
7
|
+
# for full licensing information.
|
8
|
+
#
|
9
|
+
# $Id: simpletable.rb,v 1.9 2005/06/11 14:33:17 austin Exp $
|
10
|
+
#++
|
11
|
+
require 'pdf/writer'
|
12
|
+
require 'transaction/simple/group'
|
13
|
+
|
14
|
+
# This class will create tables with a relatively simple API and internal
|
15
|
+
# implementation.
|
16
|
+
class PDF::SimpleTable
|
17
|
+
VERSION = '1.0.0'
|
18
|
+
|
19
|
+
include Transaction::Simple
|
20
|
+
|
21
|
+
# Defines formatting options for a column.
|
22
|
+
class Column
|
23
|
+
def initialize(name)
|
24
|
+
@name = name
|
25
|
+
|
26
|
+
yield self if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
# The heading of the column. This should be an instance of
|
30
|
+
# PDF::SimpleTable::Column::Heading. If it is not, it will be
|
31
|
+
# converted into one.
|
32
|
+
attr_accessor :heading
|
33
|
+
def heading=(hh) #:nodoc:
|
34
|
+
unless hh.kind_of?(Heading)
|
35
|
+
hh = Heading.new(hh)
|
36
|
+
end
|
37
|
+
@heading = hh
|
38
|
+
end
|
39
|
+
# The name of the column.
|
40
|
+
attr_reader :name
|
41
|
+
# The width of the column. If this value is set, the column will be
|
42
|
+
# exactly this number of units wide.
|
43
|
+
attr_accessor :width
|
44
|
+
# The data name that will be used to provide a hyperlink for values in
|
45
|
+
# this column.
|
46
|
+
attr_accessor :link_name
|
47
|
+
# The justification of the column. May be :left, :right, :center, or
|
48
|
+
# :full.
|
49
|
+
attr_accessor :justification
|
50
|
+
|
51
|
+
# Formatting options for heading rows. Each column can have a separate
|
52
|
+
# heading value.
|
53
|
+
class Heading
|
54
|
+
def initialize(title = nil)
|
55
|
+
@title = title
|
56
|
+
yield self if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Indicates that the heading should be rendered bold.
|
60
|
+
attr_accessor :bold
|
61
|
+
# The justification of the heading of the column. May be :left,
|
62
|
+
# :center, :right, or :full.
|
63
|
+
attr_accessor :justification
|
64
|
+
# The title of the heading. If nothing is present, the name of the
|
65
|
+
# column will be used when headings are displayed.
|
66
|
+
attr_accessor :title
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@column_order = []
|
72
|
+
@data = []
|
73
|
+
@columns = {}
|
74
|
+
|
75
|
+
@show_lines = :outer
|
76
|
+
@show_headings = true
|
77
|
+
@shade_rows = :shaded
|
78
|
+
@shade_color = Color::Grey80
|
79
|
+
@shade_color2 = Color::Grey70
|
80
|
+
@shade_headings = false
|
81
|
+
@shade_heading_color = Color::Grey90
|
82
|
+
@font_size = 10
|
83
|
+
@heading_font_size = 12
|
84
|
+
@title_font_size = 12
|
85
|
+
@title_gap = 5
|
86
|
+
@title_color = Color::Black
|
87
|
+
@heading_color = Color::Black
|
88
|
+
@text_color = Color::Black
|
89
|
+
@line_color = Color::Black
|
90
|
+
@position = :center
|
91
|
+
@orientation = :center
|
92
|
+
@bold_headings = false
|
93
|
+
|
94
|
+
@cols = PDF::Writer::OHash.new
|
95
|
+
@width = 0
|
96
|
+
@maximum_width = 0
|
97
|
+
|
98
|
+
@gap = 5
|
99
|
+
@row_gap = 2
|
100
|
+
@column_gap = 5
|
101
|
+
@header_gap = 0
|
102
|
+
|
103
|
+
@minimum_space = 0
|
104
|
+
@protect_rows = 1
|
105
|
+
@split_rows = false
|
106
|
+
|
107
|
+
@inner_line_style = PDF::Writer::StrokeStyle.new(1)
|
108
|
+
@outer_line_style = PDF::Writer::StrokeStyle.new(1)
|
109
|
+
|
110
|
+
yield self if block_given?
|
111
|
+
end
|
112
|
+
|
113
|
+
# An array of Hash entries. Each row is a Hash where the keys are the
|
114
|
+
# names of the columns as specified in #column_order and the values are
|
115
|
+
# the values of the cell.
|
116
|
+
attr_accessor :data
|
117
|
+
# An array that defines the order of the columns in the table. The
|
118
|
+
# values in this array are the column names in #data. The columns will
|
119
|
+
# be presented in the order defined here.
|
120
|
+
attr_accessor :column_order
|
121
|
+
# An array that defines columns and column options for the table. The
|
122
|
+
# entries should be PDF::SimpleTable::Column objects.
|
123
|
+
attr_accessor :columns
|
124
|
+
|
125
|
+
# The title to be put on the top of the table.
|
126
|
+
attr_accessor :title
|
127
|
+
|
128
|
+
# Whether to display the lines on the table or not. Valid values are:
|
129
|
+
#
|
130
|
+
# <tt>:none</tt>:: Displays no lines.
|
131
|
+
# <tt>:outer</tt>:: Displays outer lines only. *Default*
|
132
|
+
# <tt>:inner</tt>:: Displays inner lines only.
|
133
|
+
# <tt>:all</tt>:: Displays all lines, inner and outer.
|
134
|
+
attr_accessor :show_lines
|
135
|
+
# Displays the headings for the table if +true+. The default is +true+.
|
136
|
+
attr_accessor :show_headings
|
137
|
+
# Controls row shading.
|
138
|
+
#
|
139
|
+
# <tt>:none</tt>:: No row shading; all rows are the standard
|
140
|
+
# background colour.
|
141
|
+
# <tt>:shaded</tt>:: Alternate lines will be shaded; half of the rows
|
142
|
+
# will be the standard background colour; the rest
|
143
|
+
# of the rows will be shaded with #shade_color.
|
144
|
+
# *Default*
|
145
|
+
# <tt>:striped</tt>:: Alternate lines will be shaded; half of the rows
|
146
|
+
# will be shaded with #shade_color; the rest of the
|
147
|
+
# rows will be shaded with #shade_color2.
|
148
|
+
attr_accessor :shade_rows
|
149
|
+
# The main row shading colour. Defaults to Color::Grey80. Used with
|
150
|
+
# #shade_rows of <tt>:shaded</tt> and <tt>:striped</tt>.
|
151
|
+
attr_accessor :shade_color
|
152
|
+
# The alternate row shading colour, used with #shade_rows of
|
153
|
+
# <tt>:striped</tt>. Defaults to Color::Grey70.
|
154
|
+
attr_accessor :shade_color2
|
155
|
+
# Places a background colour in the heading if +true+.
|
156
|
+
attr_accessor :shade_headings
|
157
|
+
# Defines the colour of the background shading for the heading if
|
158
|
+
# #shade_headings is +true+. Default is Color::Grey90.
|
159
|
+
attr_accessor :shade_heading_color
|
160
|
+
# The font size of the data cells, in points. Defaults to 10 points.
|
161
|
+
attr_accessor :font_size
|
162
|
+
# The font size of the heading cells, in points. Defaults to 12 points.
|
163
|
+
attr_accessor :heading_font_size
|
164
|
+
# The font size of the title, in points. Defaults to 12 points.
|
165
|
+
attr_accessor :title_font_size
|
166
|
+
# The gap, in PDF units, between the title and the table. Defaults to 5
|
167
|
+
# units.
|
168
|
+
attr_accessor :title_gap
|
169
|
+
# The text colour of the title. Defaults to Color::Black.
|
170
|
+
attr_accessor :title_color
|
171
|
+
# The text colour of the heading. Defaults to Color::Black.
|
172
|
+
attr_accessor :heading_color
|
173
|
+
# The text colour of the body cells. Defaults to Color::Black.
|
174
|
+
attr_accessor :text_color
|
175
|
+
# The colour of the table lines. Defaults to Color::Black.
|
176
|
+
attr_accessor :line_color
|
177
|
+
# The +x+ position of the table. This will be one of:
|
178
|
+
#
|
179
|
+
# <tt>:left</tt>:: Aligned with the left margin.
|
180
|
+
# <tt>:right</tt>:: Aligned with the right margin.
|
181
|
+
# <tt>:center</tt>:: Centered between the margins. *Default*.
|
182
|
+
# <em>offset</em>:: The absolute position of the table, relative from
|
183
|
+
# the left margin.
|
184
|
+
attr_accessor :position
|
185
|
+
# The orientation of the table relative to #position.
|
186
|
+
#
|
187
|
+
# <tt>:left</tt>:: The table is to the left of #position.
|
188
|
+
# <tt>:right</tt>:: The table is to the right of #position.
|
189
|
+
# <tt>:center</tt>:: The table is centred at #position.
|
190
|
+
# <em>offset</em>:: The left of the table is offset from #position.
|
191
|
+
attr_accessor :orientation
|
192
|
+
# Makes the heading text bold if +true+. Defaults to +false+.
|
193
|
+
attr_accessor :bold_headings
|
194
|
+
# Specifies the width of the table. If the table is smaller than the
|
195
|
+
# provided width, columns are proportionally stretched to fit the width
|
196
|
+
# of the table. If the table is wider than the provided width, columns
|
197
|
+
# are proportionally shrunk to fit the width of the table. Content may
|
198
|
+
# need to wrap in this case.
|
199
|
+
#
|
200
|
+
# Defaults to zero, which indicates that the size whould be determined
|
201
|
+
# automatically based on the content and the margins.
|
202
|
+
attr_accessor :width
|
203
|
+
# Specifies the maximum width of the table. The table will not grow
|
204
|
+
# larger than this width under any circumstances.
|
205
|
+
#
|
206
|
+
# Defaults to zero, which indicates that there is no maximum width
|
207
|
+
# (aside from the margin size).
|
208
|
+
attr_accessor :maximum_width
|
209
|
+
# The space, in PDF user units, added to the top and bottom of each row
|
210
|
+
# between the text and the lines of the cell. Default 2 units.
|
211
|
+
attr_accessor :row_gap
|
212
|
+
# The space, in PDF user units, on the left and right sides of each
|
213
|
+
# cell. Default 5 units.
|
214
|
+
attr_accessor :column_gap
|
215
|
+
|
216
|
+
# The minimum space between the bottom of each row and the bottom
|
217
|
+
# margin. If the amount of space is less than this, a new page will be
|
218
|
+
# started. Default is 100 PDF user units.
|
219
|
+
attr_accessor :minimum_space
|
220
|
+
# The number of rows to hold with the heading on the page. If there are
|
221
|
+
# less than this number of rows on the page, then move the whole lot
|
222
|
+
# onto the next page. Default is one row.
|
223
|
+
attr_accessor :protect_rows
|
224
|
+
# Allows a table's rows to be split across page boundaries if +true+.
|
225
|
+
# This defaults to +false+.
|
226
|
+
attr_accessor :split_rows
|
227
|
+
# The number of PDF user units to leave open at the top of a page after
|
228
|
+
# a page break. This is typically used for a repeating page header, etc.
|
229
|
+
# Defaults to zero units.
|
230
|
+
attr_accessor :header_gap
|
231
|
+
# Defines the inner line style. The default style is a solid line with a
|
232
|
+
# thickness of 1 unit.
|
233
|
+
attr_accessor :inner_line_style
|
234
|
+
# Defines the outer line style. The default style is a solid line with a
|
235
|
+
# thickness of 1 unit.
|
236
|
+
attr_accessor :outer_line_style
|
237
|
+
|
238
|
+
# Render the table on the PDF::Writer document provided.
|
239
|
+
def render_on(pdf)
|
240
|
+
if @column_order.empty?
|
241
|
+
raise TypeError, PDF::Writer::Lang[:simpletable_columns_undefined]
|
242
|
+
end
|
243
|
+
if @data.empty?
|
244
|
+
raise TypeError, PDF::Writer::Lang[:simpletable_data_empty]
|
245
|
+
end
|
246
|
+
|
247
|
+
low_y = descender = y0 = y1 = y = nil
|
248
|
+
|
249
|
+
@cols = PDF::Writer::OHash.new
|
250
|
+
@column_order.each do |name|
|
251
|
+
col = @columns[name]
|
252
|
+
if col
|
253
|
+
@cols[name] = col
|
254
|
+
else
|
255
|
+
@cols[name] = PDF::SimpleTable::Column.new(name)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
@gap = 2 * @column_gap
|
260
|
+
|
261
|
+
max_width = __find_table_max_width__(pdf)
|
262
|
+
pos, t, x, adjustment_width, set_width = __find_table_positions__(pdf, max_width)
|
263
|
+
|
264
|
+
# if max_width is specified, and the table is too wide, and the width
|
265
|
+
# has not been set, then set the width.
|
266
|
+
if @width.zero? and @maximum_width.nonzero? and ((t - x) > @maximum_width)
|
267
|
+
@width = @maximum_width
|
268
|
+
end
|
269
|
+
|
270
|
+
if @width and (adjustment_width > 0) and (set_width < @width)
|
271
|
+
# First find the current widths of the columns involved in this
|
272
|
+
# mystery
|
273
|
+
cols0 = PDF::Writer::OHash.new
|
274
|
+
cols1 = PDF::Writer::OHash.new
|
275
|
+
|
276
|
+
xq = presentWidth = 0
|
277
|
+
last = nil
|
278
|
+
|
279
|
+
pos.each do |name, colpos|
|
280
|
+
if @cols[last].nil? or
|
281
|
+
@cols[last].width.nil? or
|
282
|
+
@cols[last].width <= 0
|
283
|
+
unless last.nil? or last.empty?
|
284
|
+
cols0[last] = colpos - xq - @gap
|
285
|
+
presentWidth += (colpos - xq - @gap)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
cols1[last] = colpos - xq
|
289
|
+
end
|
290
|
+
last = name
|
291
|
+
xq = colpos
|
292
|
+
end
|
293
|
+
|
294
|
+
# cols0 contains the widths of all the columns which are not set
|
295
|
+
needed_width = @width - set_width
|
296
|
+
|
297
|
+
# If needed width is negative then add it equally to each column,
|
298
|
+
# else get more tricky.
|
299
|
+
if presentWidth < needed_width
|
300
|
+
diff = (needed_width - presentWidth) / cols0.size.to_f
|
301
|
+
cols0.each_key { |name| cols0[name] += diff }
|
302
|
+
else
|
303
|
+
cnt = 0
|
304
|
+
loop do
|
305
|
+
break if (presentWidth <= needed_width) or (cnt >= 100)
|
306
|
+
cnt += 1 # insurance policy
|
307
|
+
# Find the widest columns and the next to widest width
|
308
|
+
aWidest = []
|
309
|
+
nWidest = widest = 0
|
310
|
+
cols0.each do |name, w|
|
311
|
+
if w > widest
|
312
|
+
aWidest = [ name ]
|
313
|
+
nWidest = widest
|
314
|
+
widest = w
|
315
|
+
elsif w == widest
|
316
|
+
aWidest << name
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Then figure out what the width of the widest columns would
|
321
|
+
# have to be to take up all the slack.
|
322
|
+
newWidestWidth = widest - (presentWidth - needed_width) / aWidest.size.to_f
|
323
|
+
if newWidestWidth > nWidest
|
324
|
+
aWidest.each { |name| cols0[name] = newWidestWidth }
|
325
|
+
presentWidth = needed_width
|
326
|
+
else
|
327
|
+
# There is no space, reduce the size of the widest ones down
|
328
|
+
# to the next size down, and we will go round again
|
329
|
+
aWidest.each { |name| cols0[name] = nWidest }
|
330
|
+
presentWidth -= (widest - nWidest) * aWidest.size
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# cols0 now contains the new widths of the constrained columns. now
|
336
|
+
# need to update the pos and max_width arrays
|
337
|
+
xq = 0
|
338
|
+
pos.each do |name, colpos|
|
339
|
+
pos[name] = xq
|
340
|
+
|
341
|
+
if @cols[name].nil? or
|
342
|
+
@cols[name].width.nil? or
|
343
|
+
@cols[name].width <= 0
|
344
|
+
if not cols0[name].nil?
|
345
|
+
xq += cols0[name] + @gap
|
346
|
+
max_width[name] = cols0[name]
|
347
|
+
end
|
348
|
+
else
|
349
|
+
xq += cols1[name] unless cols1[name].nil?
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
t = x + @width
|
354
|
+
pos[:__last_column__] = t
|
355
|
+
end
|
356
|
+
|
357
|
+
# now adjust the table to the correct location across the page
|
358
|
+
case @position
|
359
|
+
when :left
|
360
|
+
xref = pdf.absolute_left_margin
|
361
|
+
when :right
|
362
|
+
xref = pdf.absolute_right_margin
|
363
|
+
when :center
|
364
|
+
xref = pdf.margin_x_middle
|
365
|
+
else
|
366
|
+
xref = @position
|
367
|
+
end
|
368
|
+
|
369
|
+
case @orientation
|
370
|
+
when :left
|
371
|
+
dx = xref - t
|
372
|
+
when :right
|
373
|
+
dx = xref
|
374
|
+
when :center
|
375
|
+
dx = xref - (t / 2.0)
|
376
|
+
else
|
377
|
+
dx = xref + @orientation
|
378
|
+
end
|
379
|
+
|
380
|
+
pos.each { |k, v| pos[k] = v + dx }
|
381
|
+
|
382
|
+
base_x0 = x0 = x + dx
|
383
|
+
base_x1 = x1 = t + dx
|
384
|
+
|
385
|
+
base_left_margin = pdf.absolute_left_margin
|
386
|
+
base_pos = pos.dup
|
387
|
+
|
388
|
+
# Ok, just about ready to make me a table.
|
389
|
+
pdf.fill_color @text_color
|
390
|
+
pdf.stroke_color @shade_color
|
391
|
+
|
392
|
+
middle = (x0 + x1) / 2.0
|
393
|
+
|
394
|
+
# Start a transaction. This transaction will be used to regress the
|
395
|
+
# table if there are not enough rows protected.
|
396
|
+
tg = Transaction::Simple::Group.new(pdf, self)
|
397
|
+
tg.start_transaction(:table)
|
398
|
+
moved_once = false if @protect_rows.nonzero?
|
399
|
+
|
400
|
+
abortTable = true
|
401
|
+
loop do # while abortTable
|
402
|
+
break unless abortTable
|
403
|
+
abortTable = false
|
404
|
+
|
405
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
406
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
407
|
+
x0 = base_x0 + dm
|
408
|
+
x1 = base_x1 + dm
|
409
|
+
middle = (x0 + x1) / 2.0
|
410
|
+
|
411
|
+
# If the title is set, then render it.
|
412
|
+
unless @title.nil? or @title.empty?
|
413
|
+
w = pdf.text_width(@title_font_size, @title)
|
414
|
+
_y = pdf.y - pdf.font_height(@title_font_size)
|
415
|
+
if _y < pdf.absolute_bottom_margin
|
416
|
+
pdf.start_new_page
|
417
|
+
|
418
|
+
# margins may have changed on the new page
|
419
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
420
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
421
|
+
x0 = base_x0 + dm
|
422
|
+
x1 = base_x1 + dm
|
423
|
+
middle = (x0 + x1) / 2.0
|
424
|
+
end
|
425
|
+
|
426
|
+
pdf.y -= pdf.font_height(@title_font_size)
|
427
|
+
pdf.fill_color @title_color
|
428
|
+
pdf.add_text(middle - w / 2.0, pdf.y, @title_font_size, title)
|
429
|
+
pdf.y -= @title_gap
|
430
|
+
end
|
431
|
+
|
432
|
+
# Margins may have changed on the new_page.
|
433
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
434
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
435
|
+
x0 = base_x0 + dm
|
436
|
+
x1 = base_x1 + dm
|
437
|
+
middle = (x0 + x1) / 2.0
|
438
|
+
|
439
|
+
y = pdf.y # simplifies the code a bit
|
440
|
+
low_y = y if low_y.nil? or y < low_y
|
441
|
+
|
442
|
+
# Make the table
|
443
|
+
height = pdf.font_height @font_size
|
444
|
+
descender = pdf.font_descender @font_size
|
445
|
+
|
446
|
+
y0 = y + descender
|
447
|
+
dy = 0
|
448
|
+
|
449
|
+
if @show_headings
|
450
|
+
# This function will move the start of the table to a new page if
|
451
|
+
# it does not fit on this one.
|
452
|
+
hOID = __open_new_object__(pdf) if @shade_headings
|
453
|
+
pdf.fill_color @heading_color
|
454
|
+
_height, y = __table_column_headings__(pdf, pos, max_width, height,
|
455
|
+
descender, @row_gap, @heading_font_size, y)
|
456
|
+
pdf.fill_color @text_color
|
457
|
+
y0 = y + _height
|
458
|
+
y1 = y
|
459
|
+
|
460
|
+
if @shade_headings
|
461
|
+
pdf.close_object
|
462
|
+
pdf.fill_color! @shade_heading_color
|
463
|
+
pdf.rectangle(x0 - @gap / 2.0, y, x1 - x0, _height).fill
|
464
|
+
pdf.reopen_object(hOID)
|
465
|
+
pdf.close_object
|
466
|
+
pdf.restore_state
|
467
|
+
end
|
468
|
+
|
469
|
+
# Margins may have changed on the new_page
|
470
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
471
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
472
|
+
x0 = base_x0 + dm
|
473
|
+
x1 = base_x1 + dm
|
474
|
+
middle = (x0 + x1) / 2.0
|
475
|
+
else
|
476
|
+
y1 = y0
|
477
|
+
end
|
478
|
+
|
479
|
+
first_line = true
|
480
|
+
|
481
|
+
# open an object here so that the text can be put in over the
|
482
|
+
# shading
|
483
|
+
tOID = __open_new_object__(pdf) unless :none == @shade_rows
|
484
|
+
|
485
|
+
cnt = 0
|
486
|
+
cnt = 1 unless @shade_headings
|
487
|
+
newPage = false
|
488
|
+
@data.each do |row|
|
489
|
+
cnt += 1
|
490
|
+
# Start a transaction that will be used for this row to prevent it
|
491
|
+
# from being split.
|
492
|
+
unless @split_rows
|
493
|
+
pageStart = pdf.pageset.size
|
494
|
+
|
495
|
+
columnStart = pdf.column_number if pdf.columns?
|
496
|
+
|
497
|
+
tg.start_transaction(:row)
|
498
|
+
row_orig = row
|
499
|
+
y_orig = y
|
500
|
+
y0_orig = y0
|
501
|
+
y1_orig = y1
|
502
|
+
end # unless @split_rows
|
503
|
+
|
504
|
+
ok = false
|
505
|
+
second_turn = false
|
506
|
+
loop do # while !abortTable and !ok
|
507
|
+
break if abortTable or ok
|
508
|
+
|
509
|
+
mx = 0
|
510
|
+
newRow = true
|
511
|
+
|
512
|
+
loop do # while !abortTable and (newPage or newRow)
|
513
|
+
break if abortTable or not (newPage or newRow)
|
514
|
+
|
515
|
+
y -= height
|
516
|
+
low_y = y if low_y.nil? or y < low_y
|
517
|
+
|
518
|
+
if newPage or y < (pdf.absolute_bottom_margin + @minimum_space)
|
519
|
+
# check that enough rows are with the heading
|
520
|
+
moved_once = abortTable = true if @protect_rows.nonzero? and not moved_once and cnt <= @protect_rows
|
521
|
+
|
522
|
+
y2 = y - mx + (2 * height) + descender - (newRow ? 1 : 0) * height
|
523
|
+
|
524
|
+
unless :none == @show_lines
|
525
|
+
y0 = y1 unless @show_headings
|
526
|
+
|
527
|
+
__table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2,
|
528
|
+
@line_color, @inner_line_style, @outer_line_style,
|
529
|
+
@show_lines)
|
530
|
+
end
|
531
|
+
|
532
|
+
unless :none == @shade_rows
|
533
|
+
pdf.close_object
|
534
|
+
pdf.restore_state
|
535
|
+
end
|
536
|
+
|
537
|
+
pdf.start_new_page
|
538
|
+
pdf.save_state
|
539
|
+
|
540
|
+
# and the margins may have changed, this is due to the
|
541
|
+
# possibility of the columns being turned on as the columns are
|
542
|
+
# managed by manipulating the margins
|
543
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
544
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
545
|
+
x0 = base_x0 + dm
|
546
|
+
x1 = base_x1 + dm
|
547
|
+
|
548
|
+
tOID = __open_new_object__(pdf) unless :none == @shade_rows
|
549
|
+
|
550
|
+
pdf.fill_color! @text_color
|
551
|
+
|
552
|
+
y = pdf.absolute_top_margin - @header_gap
|
553
|
+
low_y = y
|
554
|
+
y0 = y + descender
|
555
|
+
mx = 0
|
556
|
+
|
557
|
+
if @show_headings
|
558
|
+
hOID = __open_new_object__(pdf) if @shade_headings
|
559
|
+
|
560
|
+
pdf.fill_color @heading_color
|
561
|
+
_height, y = __table_column_headings__(pdf, pos, max_width,
|
562
|
+
height, descender, @row_gap, @heading_font_size, y)
|
563
|
+
pdf.fill_color @text_color
|
564
|
+
|
565
|
+
y0 = y + _height
|
566
|
+
y1 = y
|
567
|
+
|
568
|
+
if @shade_headings
|
569
|
+
pdf.close_object
|
570
|
+
pdf.fill_color! @shade_heading_color
|
571
|
+
pdf.rectangle(x0 - @gap / 2, y, x1 - x0, _height).fill
|
572
|
+
pdf.reopen_object(hOID)
|
573
|
+
pdf.close_object
|
574
|
+
pdf.restore_state
|
575
|
+
end
|
576
|
+
|
577
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
578
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
579
|
+
x0 = base_x0 + dm
|
580
|
+
x1 = base_x1 + dm
|
581
|
+
middle = (x0 + x1) / 2.0
|
582
|
+
else
|
583
|
+
y1 = y0
|
584
|
+
end
|
585
|
+
|
586
|
+
first_line = true
|
587
|
+
y -= height
|
588
|
+
low_y = y if low_y.nil? or y < low_y
|
589
|
+
end
|
590
|
+
|
591
|
+
newRow = false
|
592
|
+
|
593
|
+
# Write the actual data. If these cells need to be split over
|
594
|
+
# a page, then newPage will be set, and the remaining text
|
595
|
+
# will be placed in leftOvers
|
596
|
+
newPage = false
|
597
|
+
leftOvers = PDF::Writer::OHash.new
|
598
|
+
|
599
|
+
@cols.each do |name, column|
|
600
|
+
pdf.pointer = y + height
|
601
|
+
colNewPage = false
|
602
|
+
|
603
|
+
unless row[name].nil?
|
604
|
+
lines = row[name].to_s.split(/\n/)
|
605
|
+
if column and column.link_name
|
606
|
+
lines.map! do |kk|
|
607
|
+
link = row[column.link_name]
|
608
|
+
if link
|
609
|
+
"<c:alink uri='#{link}'>#{kk}</c:alink>"
|
610
|
+
else
|
611
|
+
kk
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
else
|
616
|
+
lines = []
|
617
|
+
end
|
618
|
+
|
619
|
+
pdf.y -= @row_gap
|
620
|
+
|
621
|
+
lines.each do |line|
|
622
|
+
pdf.send(:preprocess_text, line)
|
623
|
+
start = true
|
624
|
+
|
625
|
+
loop do
|
626
|
+
break if (line.nil? or line.empty?) and not start
|
627
|
+
start = false
|
628
|
+
|
629
|
+
_y = pdf.y - height if not colNewPage
|
630
|
+
|
631
|
+
# a new page is required
|
632
|
+
newPage = colNewPage = true if _y < pdf.absolute_bottom_margin
|
633
|
+
|
634
|
+
if colNewPage
|
635
|
+
if leftOvers[name].nil?
|
636
|
+
leftOvers[name] = [line]
|
637
|
+
else
|
638
|
+
leftOvers[name] << "\n#{line}"
|
639
|
+
end
|
640
|
+
line = nil
|
641
|
+
else
|
642
|
+
if column and column.justification
|
643
|
+
just = column.justification
|
644
|
+
end
|
645
|
+
just ||= :left
|
646
|
+
|
647
|
+
pdf.y = _y
|
648
|
+
line = pdf.add_text_wrap(pos[name], pdf.y,
|
649
|
+
max_width[name], @font_size, line, just)
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
dy = y + height - pdf.y + @row_gap
|
655
|
+
mx = dy - height * (newPage ? 1 : 0) if (dy - height * (newPage ? 1 : 0)) > mx
|
656
|
+
end
|
657
|
+
|
658
|
+
# Set row to leftOvers so that they will be processed onto the
|
659
|
+
# new page
|
660
|
+
row = leftOvers
|
661
|
+
|
662
|
+
# Now add the shading underneath
|
663
|
+
unless :none == @shade_rows
|
664
|
+
pdf.close_object
|
665
|
+
|
666
|
+
if (cnt % 2).zero?
|
667
|
+
pdf.fill_color!(@shade_color)
|
668
|
+
pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
|
669
|
+
elsif (cnt % 2).nonzero? and :striped == @shade_rows
|
670
|
+
pdf.fill_color!(@shade_color2)
|
671
|
+
pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
|
672
|
+
end
|
673
|
+
pdf.reopen_object(tOID)
|
674
|
+
end
|
675
|
+
|
676
|
+
if :inner == @show_lines or :all == @show_lines
|
677
|
+
# draw a line on the top of the block
|
678
|
+
pdf.save_state
|
679
|
+
pdf.stroke_color! @line_color
|
680
|
+
if first_line
|
681
|
+
pdf.stroke_style @outer_line_style
|
682
|
+
first_line = false
|
683
|
+
else
|
684
|
+
pdf.stroke_style @inner_line_style
|
685
|
+
end
|
686
|
+
pdf.line(x0 - @gap / 2.0, y + descender + height, x1 - @gap / 2.0, y + descender + height).stroke
|
687
|
+
pdf.restore_state
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
y = y - mx + height
|
692
|
+
pdf.y = y
|
693
|
+
low_y = y if low_y.nil? or y < low_y
|
694
|
+
|
695
|
+
# checking row split over pages
|
696
|
+
unless @split_rows
|
697
|
+
if (((pdf.pageset.size != pageStart) or (pdf.columns? and columnStart != pdf.column_number)) and not second_turn)
|
698
|
+
# then we need to go back and try that again!
|
699
|
+
newPage = second_turn = true
|
700
|
+
tg.rewind_transaction(:row)
|
701
|
+
row = row_orig
|
702
|
+
low_y = y = y_orig
|
703
|
+
y0 = y0_orig
|
704
|
+
y1 = y1_orig
|
705
|
+
ok = false
|
706
|
+
|
707
|
+
dm = pdf.absolute_left_margin - base_left_margin
|
708
|
+
base_pos.each { |k, v| pos[k] = v + dm }
|
709
|
+
x0 = base_x0 + dm
|
710
|
+
x1 = base_x1 + dm
|
711
|
+
else
|
712
|
+
tg.commit_transaction(:row)
|
713
|
+
ok = true
|
714
|
+
end
|
715
|
+
else
|
716
|
+
ok = true # don't go 'round the loop if splitting rows is allowed
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
if abortTable
|
721
|
+
# abort_transaction if not ok only the outer transaction should
|
722
|
+
# be operational.
|
723
|
+
tg.rewind_transaction(:table)
|
724
|
+
pdf.start_new_page
|
725
|
+
# fix a bug where a moved table will take up the whole page.
|
726
|
+
low_y = nil
|
727
|
+
pdf.save_state
|
728
|
+
break
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
if low_y <= y
|
734
|
+
y2 = low_y + descender
|
735
|
+
else
|
736
|
+
y2 = y + descender
|
737
|
+
end
|
738
|
+
|
739
|
+
unless :none == @show_lines
|
740
|
+
y0 = y1 unless @show_headings
|
741
|
+
|
742
|
+
__table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2, @line_color,
|
743
|
+
@inner_line_style, @outer_line_style, @show_lines)
|
744
|
+
end
|
745
|
+
|
746
|
+
# close the object for drawing the text on top
|
747
|
+
unless :none == @shade_rows
|
748
|
+
pdf.close_object
|
749
|
+
pdf.restore_state
|
750
|
+
end
|
751
|
+
|
752
|
+
pdf.y = low_y
|
753
|
+
|
754
|
+
# Table has been put on the page, the rows guarded as required; commit.
|
755
|
+
tg.commit_transaction(:table)
|
756
|
+
|
757
|
+
y
|
758
|
+
rescue Exception => ex
|
759
|
+
begin
|
760
|
+
tg.abort_transaction(:table) if tg.transaction_open?
|
761
|
+
rescue
|
762
|
+
nil
|
763
|
+
end
|
764
|
+
raise ex
|
765
|
+
end
|
766
|
+
|
767
|
+
WIDTH_FACTOR = 1.01
|
768
|
+
|
769
|
+
# Find the maximum widths of the text within each column. Default to
|
770
|
+
# zero.
|
771
|
+
def __find_table_max_width__(pdf)
|
772
|
+
max_width = PDF::Writer::OHash.new(0)
|
773
|
+
|
774
|
+
# Find the maximum cell widths based on the data and the headings.
|
775
|
+
# Passing through the data multiple times is unavoidable as we must do
|
776
|
+
# some analysis first.
|
777
|
+
@data.each do |row|
|
778
|
+
@cols.each do |name, column|
|
779
|
+
w = pdf.text_width(@font_size, row[name].to_s)
|
780
|
+
w *= WIDTH_FACTOR
|
781
|
+
|
782
|
+
max_width[name] = w if w > max_width[name]
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
@cols.each do |name, column|
|
787
|
+
title = column.heading.title if column.heading
|
788
|
+
title ||= column.name
|
789
|
+
w = pdf.text_width(@heading_font_size, title)
|
790
|
+
w *= WIDTH_FACTOR
|
791
|
+
max_width[name] = w if w > max_width[name]
|
792
|
+
end
|
793
|
+
max_width
|
794
|
+
end
|
795
|
+
private :__find_table_max_width__
|
796
|
+
|
797
|
+
# Calculate the start positions of each of the columns. This is based
|
798
|
+
# on max_width, but may be modified with column options.
|
799
|
+
def __find_table_positions__(pdf, max_width)
|
800
|
+
pos = PDF::Writer::OHash.new
|
801
|
+
x = t = adjustment_width = set_width = 0
|
802
|
+
|
803
|
+
max_width.each do |name, w|
|
804
|
+
pos[name] = t
|
805
|
+
# If the column width has been specified then set that here, also
|
806
|
+
# total the width avaliable for adjustment.
|
807
|
+
if not @cols[name].nil? and
|
808
|
+
not @cols[name].width.nil? and
|
809
|
+
@cols[name].width > 0
|
810
|
+
t += @cols[name].width
|
811
|
+
max_width[name] = @cols[name].width - @gap
|
812
|
+
set_width += @cols[name].width
|
813
|
+
else
|
814
|
+
t += w + @gap
|
815
|
+
adjustment_width += w
|
816
|
+
set_width += @gap
|
817
|
+
end
|
818
|
+
end
|
819
|
+
pos[:__last_column__] = t
|
820
|
+
|
821
|
+
[pos, t, x, adjustment_width, set_width]
|
822
|
+
end
|
823
|
+
private :__find_table_positions__
|
824
|
+
|
825
|
+
# Uses ezText to add the text, and returns the height taken by the
|
826
|
+
# largest heading. This page will move the headings to a new page if
|
827
|
+
# they will not fit completely on this one transaction support will be
|
828
|
+
# used to implement this.
|
829
|
+
def __table_column_headings__(pdf, pos, max_width, height, descender, gap, size, y)
|
830
|
+
mx = second_go = 0
|
831
|
+
start_page = pdf.pageset.size
|
832
|
+
|
833
|
+
# y is the position at which the top of the table should start, so the
|
834
|
+
# base of the first text, is y-height-gap-descender, but ezText starts
|
835
|
+
# by dropping height.
|
836
|
+
|
837
|
+
# The return from this function is the total cell height, including
|
838
|
+
# gaps, and y is adjusted to be the postion of the bottom line.
|
839
|
+
tg = Transaction::Simple::Group.new(pdf, self)
|
840
|
+
tg.start_transaction(:column_headings)
|
841
|
+
|
842
|
+
ok = false
|
843
|
+
y -= gap
|
844
|
+
loop do
|
845
|
+
break if ok
|
846
|
+
@cols.each do |name, column|
|
847
|
+
pdf.pointer = y
|
848
|
+
|
849
|
+
if column.heading
|
850
|
+
justification = column.heading.justification
|
851
|
+
bold = column.heading.bold
|
852
|
+
title = column.heading.title
|
853
|
+
end
|
854
|
+
|
855
|
+
justification ||= :left
|
856
|
+
bold ||= @bold_headings
|
857
|
+
title ||= column.name
|
858
|
+
|
859
|
+
title = "<b>#{title}</b>" if bold
|
860
|
+
|
861
|
+
pdf.text(title, :font_size => size, :absolute_left => pos[name],
|
862
|
+
:absolute_right => (max_width[name] + pos[name]),
|
863
|
+
:justification => justification)
|
864
|
+
dy = y - pdf.y
|
865
|
+
mx = dy if dy > mx
|
866
|
+
end
|
867
|
+
|
868
|
+
y -= (mx + gap) - descender # y = y - mx - gap + descender
|
869
|
+
|
870
|
+
# If this has been moved to a new page, then abort the transaction;
|
871
|
+
# move to a new page, and put it there. Do not check on the second
|
872
|
+
# time around to avoid an infinite loop.
|
873
|
+
if (pdf.pageset.size != start_page and not second_go)
|
874
|
+
tg.rewind_transaction(:column_headings)
|
875
|
+
|
876
|
+
pdf.start_new_page
|
877
|
+
save_state
|
878
|
+
y = @y - gap - descender
|
879
|
+
ok = false
|
880
|
+
second_go = true
|
881
|
+
mx = 0
|
882
|
+
else
|
883
|
+
tg.commit_transaction(:column_headings)
|
884
|
+
ok = true
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
return [mx + gap * 2 - descender, y]
|
889
|
+
rescue Exception => ex
|
890
|
+
begin
|
891
|
+
tg.abort_transaction(:column_headings) if tg.transaction_open?(:column_headings)
|
892
|
+
rescue
|
893
|
+
nil
|
894
|
+
end
|
895
|
+
raise ex
|
896
|
+
end
|
897
|
+
private :__table_column_headings__
|
898
|
+
|
899
|
+
def __table_draw_lines__(pdf, pos, gap, x0, x1, y0, y1, y2, col, inner, outer, opt = :outer)
|
900
|
+
x0 = 1000
|
901
|
+
x1 = 0
|
902
|
+
|
903
|
+
pdf.stroke_color(col)
|
904
|
+
|
905
|
+
cnt = 0
|
906
|
+
n = pos.size
|
907
|
+
|
908
|
+
pos.each do |name, x|
|
909
|
+
cnt += 1
|
910
|
+
|
911
|
+
if (cnt == 1 or cnt == n)
|
912
|
+
pdf.stroke_style outer
|
913
|
+
else
|
914
|
+
pdf.stroke_style inner
|
915
|
+
end
|
916
|
+
|
917
|
+
pdf.line(x - gap / 2.0, y0, x - gap / 2.0, y2).stroke
|
918
|
+
x1 = x if x > x1
|
919
|
+
x0 = x if x < x0
|
920
|
+
end
|
921
|
+
|
922
|
+
pdf.stroke_style outer
|
923
|
+
|
924
|
+
pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y0,
|
925
|
+
x1 - (gap / 2.0) + (outer.width / 2.0), y0).stroke
|
926
|
+
|
927
|
+
# Only do the second line if it is different than the first AND each
|
928
|
+
# row does not have a line on it.
|
929
|
+
if y0 != y1 and @show_lines == :outer
|
930
|
+
pdf.line(x0 - gap / 2.0, y1, x1 - gap / 2.0, y1).stroke
|
931
|
+
end
|
932
|
+
pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y2,
|
933
|
+
x1 - (gap / 2.0) + (outer.width / 2.0), y2).stroke
|
934
|
+
end
|
935
|
+
private :__table_draw_lines__
|
936
|
+
|
937
|
+
def __open_new_object__(pdf)
|
938
|
+
pdf.save_state
|
939
|
+
tOID = pdf.open_object
|
940
|
+
pdf.close_object
|
941
|
+
pdf.add_object(tOID)
|
942
|
+
pdf.reopen_object(tOID)
|
943
|
+
tOID
|
944
|
+
end
|
945
|
+
private :__open_new_object__
|
946
|
+
end
|