pdf-writer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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