metaskills-pdf-writer 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/ChangeLog +113 -0
  2. data/LICENCE +131 -0
  3. data/README +33 -0
  4. data/bin/techbook +24 -0
  5. data/demo/chunkybacon.rb +36 -0
  6. data/demo/code.rb +71 -0
  7. data/demo/colornames.rb +47 -0
  8. data/demo/demo.rb +73 -0
  9. data/demo/gettysburg.rb +66 -0
  10. data/demo/hello.rb +26 -0
  11. data/demo/individual-i.rb +89 -0
  12. data/demo/pac.rb +70 -0
  13. data/demo/qr-language.rb +580 -0
  14. data/demo/qr-library.rb +380 -0
  15. data/images/bluesmoke.jpg +0 -0
  16. data/images/chunkybacon.jpg +0 -0
  17. data/images/chunkybacon.png +0 -0
  18. data/lib/pdf/charts.rb +13 -0
  19. data/lib/pdf/charts/stddev.rb +431 -0
  20. data/lib/pdf/core_ext/mutex.rb +11 -0
  21. data/lib/pdf/math.rb +108 -0
  22. data/lib/pdf/quickref.rb +333 -0
  23. data/lib/pdf/simpletable.rb +948 -0
  24. data/lib/pdf/techbook.rb +901 -0
  25. data/lib/pdf/writer.rb +2730 -0
  26. data/lib/pdf/writer/arc4.rb +63 -0
  27. data/lib/pdf/writer/fontmetrics.rb +203 -0
  28. data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
  29. data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  30. data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  31. data/lib/pdf/writer/fonts/Courier.afm +342 -0
  32. data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  33. data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  34. data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  35. data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
  36. data/lib/pdf/writer/fonts/MustRead.html +19 -0
  37. data/lib/pdf/writer/fonts/Symbol.afm +213 -0
  38. data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
  39. data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  40. data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
  41. data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
  42. data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  43. data/lib/pdf/writer/graphics.rb +813 -0
  44. data/lib/pdf/writer/graphics/imageinfo.rb +365 -0
  45. data/lib/pdf/writer/lang.rb +43 -0
  46. data/lib/pdf/writer/lang/en.rb +99 -0
  47. data/lib/pdf/writer/object.rb +23 -0
  48. data/lib/pdf/writer/object/action.rb +35 -0
  49. data/lib/pdf/writer/object/annotation.rb +42 -0
  50. data/lib/pdf/writer/object/catalog.rb +39 -0
  51. data/lib/pdf/writer/object/contents.rb +70 -0
  52. data/lib/pdf/writer/object/destination.rb +40 -0
  53. data/lib/pdf/writer/object/encryption.rb +53 -0
  54. data/lib/pdf/writer/object/font.rb +72 -0
  55. data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
  56. data/lib/pdf/writer/object/fontencoding.rb +40 -0
  57. data/lib/pdf/writer/object/image.rb +304 -0
  58. data/lib/pdf/writer/object/info.rb +51 -0
  59. data/lib/pdf/writer/object/outline.rb +30 -0
  60. data/lib/pdf/writer/object/outlines.rb +30 -0
  61. data/lib/pdf/writer/object/page.rb +195 -0
  62. data/lib/pdf/writer/object/pages.rb +115 -0
  63. data/lib/pdf/writer/object/procset.rb +46 -0
  64. data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
  65. data/lib/pdf/writer/ohash.rb +58 -0
  66. data/lib/pdf/writer/oreader.rb +25 -0
  67. data/lib/pdf/writer/state.rb +48 -0
  68. data/lib/pdf/writer/strokestyle.rb +138 -0
  69. data/manual.pwd +5965 -0
  70. metadata +156 -0
@@ -0,0 +1,948 @@
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$
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.2.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::RGB::Grey80
79
+ @shade_color2 = Color::RGB::Grey70
80
+ @shade_headings = false
81
+ @shade_heading_color = Color::RGB::Grey90
82
+ @font_size = 10
83
+ @heading_font_size = 12
84
+ @title_font_size = 12
85
+ @title_gap = 5
86
+ @title_color = Color::RGB::Black
87
+ @heading_color = Color::RGB::Black
88
+ @text_color = Color::RGB::Black
89
+ @line_color = Color::RGB::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::RGB::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::RGB::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::RGB::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::RGB::Black.
170
+ attr_accessor :title_color
171
+ # The text colour of the heading. Defaults to Color::RGB::Black.
172
+ attr_accessor :heading_color
173
+ # The text colour of the body cells. Defaults to Color::RGB::Black.
174
+ attr_accessor :text_color
175
+ # The colour of the table lines. Defaults to Color::RGB::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, @title_font_size)
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, @title_font_size)
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
+ old_y = y
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.fill_color! @shade_heading_color
570
+ pdf.rectangle(x0 - @gap / 2, y, x1 - x0, _height).fill
571
+ pdf.fill_color @heading_color
572
+ __table_column_headings__(pdf, pos, max_width, height,
573
+ descender, @row_gap,
574
+ @heading_font_size, old_y)
575
+ pdf.fill_color @text_color
576
+ end
577
+
578
+ dm = pdf.absolute_left_margin - base_left_margin
579
+ base_pos.each { |k, v| pos[k] = v + dm }
580
+ x0 = base_x0 + dm
581
+ x1 = base_x1 + dm
582
+ middle = (x0 + x1) / 2.0
583
+ else
584
+ y1 = y0
585
+ end
586
+
587
+ first_line = true
588
+ y -= height
589
+ low_y = y if low_y.nil? or y < low_y
590
+ end
591
+
592
+ newRow = false
593
+
594
+ # Write the actual data. If these cells need to be split over
595
+ # a page, then newPage will be set, and the remaining text
596
+ # will be placed in leftOvers
597
+ newPage = false
598
+ leftOvers = PDF::Writer::OHash.new
599
+
600
+ @cols.each do |name, column|
601
+ pdf.pointer = y + height
602
+ colNewPage = false
603
+
604
+ unless row[name].nil?
605
+ lines = row[name].to_s.split(/\n/)
606
+ if column and column.link_name
607
+ lines.map! do |kk|
608
+ link = row[column.link_name]
609
+ if link
610
+ "<c:alink uri='#{link}'>#{kk}</c:alink>"
611
+ else
612
+ kk
613
+ end
614
+ end
615
+ end
616
+ else
617
+ lines = []
618
+ end
619
+
620
+ pdf.y -= @row_gap
621
+
622
+ lines.each do |line|
623
+ pdf.send(:preprocess_text, line)
624
+ start = true
625
+
626
+ loop do
627
+ break if (line.nil? or line.empty?) and not start
628
+ start = false
629
+
630
+ _y = pdf.y - height if not colNewPage
631
+
632
+ # a new page is required
633
+ newPage = colNewPage = true if _y < pdf.absolute_bottom_margin
634
+
635
+ if colNewPage
636
+ if leftOvers[name].nil?
637
+ leftOvers[name] = [line]
638
+ else
639
+ leftOvers[name] << "\n#{line}"
640
+ end
641
+ line = nil
642
+ else
643
+ if column and column.justification
644
+ just = column.justification
645
+ end
646
+ just ||= :left
647
+
648
+ pdf.y = _y
649
+ line = pdf.add_text_wrap(pos[name], pdf.y,
650
+ max_width[name], line,
651
+ @font_size, just)
652
+ end
653
+ end
654
+ end
655
+
656
+ dy = y + height - pdf.y + @row_gap
657
+ mx = dy - height * (newPage ? 1 : 0) if (dy - height * (newPage ? 1 : 0)) > mx
658
+ end
659
+
660
+ # Set row to leftOvers so that they will be processed onto the
661
+ # new page
662
+ row = leftOvers
663
+
664
+ # Now add the shading underneath
665
+ unless :none == @shade_rows
666
+ pdf.close_object
667
+
668
+ if (cnt % 2).zero?
669
+ pdf.fill_color!(@shade_color)
670
+ pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
671
+ elsif (cnt % 2).nonzero? and :striped == @shade_rows
672
+ pdf.fill_color!(@shade_color2)
673
+ pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
674
+ end
675
+ pdf.reopen_object(tOID)
676
+ end
677
+
678
+ if :inner == @show_lines or :all == @show_lines
679
+ # draw a line on the top of the block
680
+ pdf.save_state
681
+ pdf.stroke_color! @line_color
682
+ if first_line
683
+ pdf.stroke_style @outer_line_style
684
+ first_line = false
685
+ else
686
+ pdf.stroke_style @inner_line_style
687
+ end
688
+ pdf.line(x0 - @gap / 2.0, y + descender + height, x1 - @gap / 2.0, y + descender + height).stroke
689
+ pdf.restore_state
690
+ end
691
+ end
692
+
693
+ y = y - mx + height
694
+ pdf.y = y
695
+ low_y = y if low_y.nil? or y < low_y
696
+
697
+ # checking row split over pages
698
+ unless @split_rows
699
+ if (((pdf.pageset.size != pageStart) or (pdf.columns? and columnStart != pdf.column_number)) and not second_turn)
700
+ # then we need to go back and try that again!
701
+ newPage = second_turn = true
702
+ tg.rewind_transaction(:row)
703
+ row = row_orig
704
+ low_y = y = y_orig
705
+ y0 = y0_orig
706
+ y1 = y1_orig
707
+ ok = false
708
+
709
+ dm = pdf.absolute_left_margin - base_left_margin
710
+ base_pos.each { |k, v| pos[k] = v + dm }
711
+ x0 = base_x0 + dm
712
+ x1 = base_x1 + dm
713
+ else
714
+ tg.commit_transaction(:row)
715
+ ok = true
716
+ end
717
+ else
718
+ ok = true # don't go 'round the loop if splitting rows is allowed
719
+ end
720
+ end
721
+
722
+ if abortTable
723
+ # abort_transaction if not ok only the outer transaction should
724
+ # be operational.
725
+ tg.rewind_transaction(:table)
726
+ pdf.start_new_page
727
+ # fix a bug where a moved table will take up the whole page.
728
+ low_y = nil
729
+ pdf.save_state
730
+ break
731
+ end
732
+ end
733
+ end
734
+
735
+ if low_y <= y
736
+ y2 = low_y + descender
737
+ else
738
+ y2 = y + descender
739
+ end
740
+
741
+ unless :none == @show_lines
742
+ y0 = y1 unless @show_headings
743
+
744
+ __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2, @line_color,
745
+ @inner_line_style, @outer_line_style, @show_lines)
746
+ end
747
+
748
+ # close the object for drawing the text on top
749
+ unless :none == @shade_rows
750
+ pdf.close_object
751
+ pdf.restore_state
752
+ end
753
+
754
+ pdf.y = low_y
755
+
756
+ # Table has been put on the page, the rows guarded as required; commit.
757
+ tg.commit_transaction(:table)
758
+
759
+ y
760
+ rescue Exception => ex
761
+ begin
762
+ tg.abort_transaction(:table) if tg.transaction_open?
763
+ rescue
764
+ nil
765
+ end
766
+ raise ex
767
+ end
768
+
769
+ WIDTH_FACTOR = 1.01
770
+
771
+ # Find the maximum widths of the text within each column. Default to
772
+ # zero.
773
+ def __find_table_max_width__(pdf)
774
+ max_width = PDF::Writer::OHash.new(-1)
775
+
776
+ # Find the maximum cell widths based on the data and the headings.
777
+ # Passing through the data multiple times is unavoidable as we must
778
+ # do some analysis first.
779
+ @data.each do |row|
780
+ @cols.each do |name, column|
781
+ w = pdf.text_width(row[name].to_s, @font_size)
782
+ w *= PDF::SimpleTable::WIDTH_FACTOR
783
+
784
+ max_width[name] = w if w > max_width[name]
785
+ end
786
+ end
787
+
788
+ @cols.each do |name, column|
789
+ title = column.heading.title if column.heading
790
+ title ||= column.name
791
+ w = pdf.text_width(title, @heading_font_size)
792
+ w *= PDF::SimpleTable::WIDTH_FACTOR
793
+ max_width[name] = w if w > max_width[name]
794
+ end
795
+ max_width
796
+ end
797
+ private :__find_table_max_width__
798
+
799
+ # Calculate the start positions of each of the columns. This is based
800
+ # on max_width, but may be modified with column options.
801
+ def __find_table_positions__(pdf, max_width)
802
+ pos = PDF::Writer::OHash.new
803
+ x = t = adjustment_width = set_width = 0
804
+
805
+ max_width.each do |name, w|
806
+ pos[name] = t
807
+ # If the column width has been specified then set that here, also
808
+ # total the width avaliable for adjustment.
809
+ if not @cols[name].nil? and
810
+ not @cols[name].width.nil? and
811
+ @cols[name].width > 0
812
+ t += @cols[name].width
813
+ max_width[name] = @cols[name].width - @gap
814
+ set_width += @cols[name].width
815
+ else
816
+ t += w + @gap
817
+ adjustment_width += w
818
+ set_width += @gap
819
+ end
820
+ end
821
+ pos[:__last_column__] = t
822
+
823
+ [pos, t, x, adjustment_width, set_width]
824
+ end
825
+ private :__find_table_positions__
826
+
827
+ # Uses ezText to add the text, and returns the height taken by the
828
+ # largest heading. This page will move the headings to a new page if
829
+ # they will not fit completely on this one transaction support will be
830
+ # used to implement this.
831
+ def __table_column_headings__(pdf, pos, max_width, height, descender, gap, size, y)
832
+ mx = second_go = 0
833
+ start_page = pdf.pageset.size
834
+
835
+ # y is the position at which the top of the table should start, so the
836
+ # base of the first text, is y-height-gap-descender, but ezText starts
837
+ # by dropping height.
838
+
839
+ # The return from this function is the total cell height, including
840
+ # gaps, and y is adjusted to be the postion of the bottom line.
841
+ tg = Transaction::Simple::Group.new(pdf, self)
842
+ tg.start_transaction(:column_headings)
843
+
844
+ ok = false
845
+ y -= gap
846
+ loop do
847
+ break if ok
848
+ @cols.each do |name, column|
849
+ pdf.pointer = y
850
+
851
+ if column.heading
852
+ justification = column.heading.justification
853
+ bold = column.heading.bold
854
+ title = column.heading.title
855
+ end
856
+
857
+ justification ||= :left
858
+ bold ||= @bold_headings
859
+ title ||= column.name
860
+
861
+ title = "<b>#{title}</b>" if bold
862
+
863
+ pdf.text(title, :font_size => size, :absolute_left => pos[name],
864
+ :absolute_right => (max_width[name] + pos[name]),
865
+ :justification => justification)
866
+ dy = y - pdf.y
867
+ mx = dy if dy > mx
868
+ end
869
+
870
+ y -= (mx + gap) - descender # y = y - mx - gap + descender
871
+
872
+ # If this has been moved to a new page, then abort the transaction;
873
+ # move to a new page, and put it there. Do not check on the second
874
+ # time around to avoid an infinite loop.
875
+ if (pdf.pageset.size != start_page and not second_go)
876
+ tg.rewind_transaction(:column_headings)
877
+
878
+ pdf.start_new_page
879
+ save_state
880
+ y = @y - gap - descender
881
+ ok = false
882
+ second_go = true
883
+ mx = 0
884
+ else
885
+ tg.commit_transaction(:column_headings)
886
+ ok = true
887
+ end
888
+ end
889
+
890
+ return [mx + gap * 2 - descender, y]
891
+ rescue Exception => ex
892
+ begin
893
+ tg.abort_transaction(:column_headings) if tg.transaction_open?(:column_headings)
894
+ rescue
895
+ nil
896
+ end
897
+ raise ex
898
+ end
899
+ private :__table_column_headings__
900
+
901
+ def __table_draw_lines__(pdf, pos, gap, x0, x1, y0, y1, y2, col, inner, outer, opt = :outer)
902
+ x0 = 1000
903
+ x1 = 0
904
+
905
+ pdf.stroke_color(col)
906
+
907
+ cnt = 0
908
+ n = pos.size
909
+
910
+ pos.each do |name, x|
911
+ cnt += 1
912
+
913
+ if (cnt == 1 or cnt == n)
914
+ pdf.stroke_style outer
915
+ else
916
+ pdf.stroke_style inner
917
+ end
918
+
919
+ pdf.line(x - gap / 2.0, y0, x - gap / 2.0, y2).stroke
920
+ x1 = x if x > x1
921
+ x0 = x if x < x0
922
+ end
923
+
924
+ pdf.stroke_style outer
925
+
926
+ pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y0,
927
+ x1 - (gap / 2.0) + (outer.width / 2.0), y0).stroke
928
+
929
+ # Only do the second line if it is different than the first AND each
930
+ # row does not have a line on it.
931
+ if y0 != y1 and @show_lines == :outer
932
+ pdf.line(x0 - gap / 2.0, y1, x1 - gap / 2.0, y1).stroke
933
+ end
934
+ pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y2,
935
+ x1 - (gap / 2.0) + (outer.width / 2.0), y2).stroke
936
+ end
937
+ private :__table_draw_lines__
938
+
939
+ def __open_new_object__(pdf)
940
+ pdf.save_state
941
+ tOID = pdf.open_object
942
+ pdf.close_object
943
+ pdf.add_object(tOID)
944
+ pdf.reopen_object(tOID)
945
+ tOID
946
+ end
947
+ private :__open_new_object__
948
+ end