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.
Files changed (74) hide show
  1. data/ChangeLog +44 -0
  2. data/LICENCE +118 -0
  3. data/README +32 -0
  4. data/bin/loader +54 -0
  5. data/bin/manual +22 -0
  6. data/bin/manual.bat +2 -0
  7. data/demo/chunkybacon.rb +28 -0
  8. data/demo/code.rb +63 -0
  9. data/demo/colornames.rb +843 -0
  10. data/demo/demo.rb +65 -0
  11. data/demo/gettysburg.rb +58 -0
  12. data/demo/hello.rb +18 -0
  13. data/demo/individual-i.rb +81 -0
  14. data/demo/pac.rb +62 -0
  15. data/demo/pagenumber.rb +67 -0
  16. data/demo/qr-language.rb +573 -0
  17. data/demo/qr-library.rb +371 -0
  18. data/images/chunkybacon.jpg +0 -0
  19. data/images/chunkybacon.png +0 -0
  20. data/images/pdfwriter-icon.jpg +0 -0
  21. data/images/pdfwriter-small.jpg +0 -0
  22. data/lib/pdf/charts.rb +13 -0
  23. data/lib/pdf/charts/stddev.rb +431 -0
  24. data/lib/pdf/grid.rb +135 -0
  25. data/lib/pdf/math.rb +108 -0
  26. data/lib/pdf/quickref.rb +330 -0
  27. data/lib/pdf/simpletable.rb +946 -0
  28. data/lib/pdf/techbook.rb +890 -0
  29. data/lib/pdf/writer.rb +2661 -0
  30. data/lib/pdf/writer/arc4.rb +63 -0
  31. data/lib/pdf/writer/fontmetrics.rb +201 -0
  32. data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
  33. data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  34. data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  35. data/lib/pdf/writer/fonts/Courier.afm +342 -0
  36. data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  37. data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  38. data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  39. data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
  40. data/lib/pdf/writer/fonts/MustRead.html +1 -0
  41. data/lib/pdf/writer/fonts/Symbol.afm +213 -0
  42. data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
  43. data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  44. data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
  45. data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
  46. data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  47. data/lib/pdf/writer/graphics.rb +727 -0
  48. data/lib/pdf/writer/graphics/imageinfo.rb +365 -0
  49. data/lib/pdf/writer/lang.rb +43 -0
  50. data/lib/pdf/writer/lang/en.rb +77 -0
  51. data/lib/pdf/writer/object.rb +23 -0
  52. data/lib/pdf/writer/object/action.rb +40 -0
  53. data/lib/pdf/writer/object/annotation.rb +42 -0
  54. data/lib/pdf/writer/object/catalog.rb +39 -0
  55. data/lib/pdf/writer/object/contents.rb +68 -0
  56. data/lib/pdf/writer/object/destination.rb +40 -0
  57. data/lib/pdf/writer/object/encryption.rb +53 -0
  58. data/lib/pdf/writer/object/font.rb +76 -0
  59. data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
  60. data/lib/pdf/writer/object/fontencoding.rb +39 -0
  61. data/lib/pdf/writer/object/image.rb +168 -0
  62. data/lib/pdf/writer/object/info.rb +55 -0
  63. data/lib/pdf/writer/object/outline.rb +30 -0
  64. data/lib/pdf/writer/object/outlines.rb +30 -0
  65. data/lib/pdf/writer/object/page.rb +195 -0
  66. data/lib/pdf/writer/object/pages.rb +115 -0
  67. data/lib/pdf/writer/object/procset.rb +46 -0
  68. data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
  69. data/lib/pdf/writer/ohash.rb +58 -0
  70. data/lib/pdf/writer/oreader.rb +25 -0
  71. data/lib/pdf/writer/state.rb +48 -0
  72. data/lib/pdf/writer/strokestyle.rb +138 -0
  73. data/manual.pwd +5151 -0
  74. 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