pdf-writer 1.0.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/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
|