pdf-labels 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 (142) hide show
  1. data/History.txt +8 -0
  2. data/LICENCE +38 -0
  3. data/Manifest.txt +141 -0
  4. data/README.txt +72 -0
  5. data/Rakefile +30 -0
  6. data/lib/alias.rb +8 -0
  7. data/lib/glabel_template.rb +36 -0
  8. data/lib/label.rb +52 -0
  9. data/lib/layout.rb +13 -0
  10. data/lib/length_node.rb +47 -0
  11. data/lib/markup.rb +25 -0
  12. data/lib/pdf_label_page.rb +171 -0
  13. data/lib/pdf_labels.rb +6 -0
  14. data/lib/template.rb +37 -0
  15. data/templates/avery-iso-templates.xml +222 -0
  16. data/templates/avery-other-templates.xml +21 -0
  17. data/templates/avery-us-templates.xml +599 -0
  18. data/templates/glabels-2.0.dtd +329 -0
  19. data/templates/misc-iso-templates.xml +434 -0
  20. data/templates/misc-other-templates.xml +21 -0
  21. data/templates/misc-us-templates.xml +183 -0
  22. data/templates/paper-sizes.xml +37 -0
  23. data/templates/zweckform-iso-templates.xml +197 -0
  24. data/test/test_pdf_label_page.rb +91 -0
  25. data/vendor/color.rb +87 -0
  26. data/vendor/color/cmyk.rb +182 -0
  27. data/vendor/color/css.rb +27 -0
  28. data/vendor/color/grayscale.rb +135 -0
  29. data/vendor/color/hsl.rb +130 -0
  30. data/vendor/color/palette.rb +15 -0
  31. data/vendor/color/palette/gimp.rb +107 -0
  32. data/vendor/color/palette/monocontrast.rb +180 -0
  33. data/vendor/color/rgb-colors.rb +189 -0
  34. data/vendor/color/rgb.rb +311 -0
  35. data/vendor/color/rgb/metallic.rb +28 -0
  36. data/vendor/color/yiq.rb +78 -0
  37. data/vendor/pdf/charts.rb +13 -0
  38. data/vendor/pdf/charts/stddev.rb +433 -0
  39. data/vendor/pdf/grid.rb +135 -0
  40. data/vendor/pdf/math.rb +108 -0
  41. data/vendor/pdf/pagenumbers.rb +288 -0
  42. data/vendor/pdf/quickref.rb +331 -0
  43. data/vendor/pdf/simpletable.rb +947 -0
  44. data/vendor/pdf/techbook.rb +901 -0
  45. data/vendor/pdf/writer.rb +2801 -0
  46. data/vendor/pdf/writer/arc4.rb +63 -0
  47. data/vendor/pdf/writer/fontmetrics.rb +202 -0
  48. data/vendor/pdf/writer/fonts/Courier-Bold.afm +342 -0
  49. data/vendor/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  50. data/vendor/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  51. data/vendor/pdf/writer/fonts/Courier.afm +342 -0
  52. data/vendor/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  53. data/vendor/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  54. data/vendor/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  55. data/vendor/pdf/writer/fonts/Helvetica.afm +3051 -0
  56. data/vendor/pdf/writer/fonts/Symbol.afm +213 -0
  57. data/vendor/pdf/writer/fonts/Times-Bold.afm +2588 -0
  58. data/vendor/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  59. data/vendor/pdf/writer/fonts/Times-Italic.afm +2667 -0
  60. data/vendor/pdf/writer/fonts/Times-Roman.afm +2419 -0
  61. data/vendor/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  62. data/vendor/pdf/writer/graphics.rb +813 -0
  63. data/vendor/pdf/writer/graphics/imageinfo.rb +365 -0
  64. data/vendor/pdf/writer/lang.rb +44 -0
  65. data/vendor/pdf/writer/lang/en.rb +104 -0
  66. data/vendor/pdf/writer/object.rb +23 -0
  67. data/vendor/pdf/writer/object/action.rb +40 -0
  68. data/vendor/pdf/writer/object/annotation.rb +42 -0
  69. data/vendor/pdf/writer/object/catalog.rb +39 -0
  70. data/vendor/pdf/writer/object/contents.rb +69 -0
  71. data/vendor/pdf/writer/object/destination.rb +40 -0
  72. data/vendor/pdf/writer/object/encryption.rb +53 -0
  73. data/vendor/pdf/writer/object/font.rb +68 -0
  74. data/vendor/pdf/writer/object/fontdescriptor.rb +34 -0
  75. data/vendor/pdf/writer/object/fontencoding.rb +40 -0
  76. data/vendor/pdf/writer/object/image.rb +308 -0
  77. data/vendor/pdf/writer/object/info.rb +79 -0
  78. data/vendor/pdf/writer/object/outline.rb +30 -0
  79. data/vendor/pdf/writer/object/outlines.rb +30 -0
  80. data/vendor/pdf/writer/object/page.rb +195 -0
  81. data/vendor/pdf/writer/object/pages.rb +115 -0
  82. data/vendor/pdf/writer/object/procset.rb +46 -0
  83. data/vendor/pdf/writer/object/viewerpreferences.rb +74 -0
  84. data/vendor/pdf/writer/ohash.rb +58 -0
  85. data/vendor/pdf/writer/oreader.rb +25 -0
  86. data/vendor/pdf/writer/state.rb +48 -0
  87. data/vendor/pdf/writer/strokestyle.rb +140 -0
  88. data/vendor/transaction/simple.rb +693 -0
  89. data/vendor/transaction/simple/group.rb +133 -0
  90. data/vendor/transaction/simple/threadsafe.rb +52 -0
  91. data/vendor/transaction/simple/threadsafe/group.rb +23 -0
  92. data/vendor/xml-mapping/ChangeLog +128 -0
  93. data/vendor/xml-mapping/LICENSE +56 -0
  94. data/vendor/xml-mapping/README +386 -0
  95. data/vendor/xml-mapping/README_XPATH +175 -0
  96. data/vendor/xml-mapping/Rakefile +214 -0
  97. data/vendor/xml-mapping/TODO.txt +32 -0
  98. data/vendor/xml-mapping/doc/xpath_impl_notes.txt +119 -0
  99. data/vendor/xml-mapping/examples/company.rb +34 -0
  100. data/vendor/xml-mapping/examples/company.xml +26 -0
  101. data/vendor/xml-mapping/examples/company_usage.intin.rb +19 -0
  102. data/vendor/xml-mapping/examples/company_usage.intout +39 -0
  103. data/vendor/xml-mapping/examples/order.rb +61 -0
  104. data/vendor/xml-mapping/examples/order.xml +54 -0
  105. data/vendor/xml-mapping/examples/order_signature_enhanced.rb +7 -0
  106. data/vendor/xml-mapping/examples/order_signature_enhanced.xml +9 -0
  107. data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intin.rb +12 -0
  108. data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intout +16 -0
  109. data/vendor/xml-mapping/examples/order_usage.intin.rb +73 -0
  110. data/vendor/xml-mapping/examples/order_usage.intout +147 -0
  111. data/vendor/xml-mapping/examples/time_augm.intin.rb +19 -0
  112. data/vendor/xml-mapping/examples/time_augm.intout +23 -0
  113. data/vendor/xml-mapping/examples/time_node.rb +27 -0
  114. data/vendor/xml-mapping/examples/xpath_create_new.intin.rb +85 -0
  115. data/vendor/xml-mapping/examples/xpath_create_new.intout +181 -0
  116. data/vendor/xml-mapping/examples/xpath_docvsroot.intin.rb +30 -0
  117. data/vendor/xml-mapping/examples/xpath_docvsroot.intout +34 -0
  118. data/vendor/xml-mapping/examples/xpath_ensure_created.intin.rb +62 -0
  119. data/vendor/xml-mapping/examples/xpath_ensure_created.intout +114 -0
  120. data/vendor/xml-mapping/examples/xpath_pathological.intin.rb +42 -0
  121. data/vendor/xml-mapping/examples/xpath_pathological.intout +56 -0
  122. data/vendor/xml-mapping/examples/xpath_usage.intin.rb +51 -0
  123. data/vendor/xml-mapping/examples/xpath_usage.intout +57 -0
  124. data/vendor/xml-mapping/install.rb +40 -0
  125. data/vendor/xml-mapping/lib/xml/mapping.rb +14 -0
  126. data/vendor/xml-mapping/lib/xml/mapping/base.rb +571 -0
  127. data/vendor/xml-mapping/lib/xml/mapping/standard_nodes.rb +343 -0
  128. data/vendor/xml-mapping/lib/xml/mapping/version.rb +8 -0
  129. data/vendor/xml-mapping/lib/xml/xxpath.rb +354 -0
  130. data/vendor/xml-mapping/test/all_tests.rb +6 -0
  131. data/vendor/xml-mapping/test/company.rb +56 -0
  132. data/vendor/xml-mapping/test/documents_folders.rb +33 -0
  133. data/vendor/xml-mapping/test/fixtures/bookmarks1.xml +24 -0
  134. data/vendor/xml-mapping/test/fixtures/company1.xml +85 -0
  135. data/vendor/xml-mapping/test/fixtures/documents_folders.xml +71 -0
  136. data/vendor/xml-mapping/test/fixtures/documents_folders2.xml +30 -0
  137. data/vendor/xml-mapping/test/multiple_mappings.rb +80 -0
  138. data/vendor/xml-mapping/test/tests_init.rb +2 -0
  139. data/vendor/xml-mapping/test/xml_mapping_adv_test.rb +84 -0
  140. data/vendor/xml-mapping/test/xml_mapping_test.rb +201 -0
  141. data/vendor/xml-mapping/test/xpath_test.rb +273 -0
  142. metadata +191 -0
@@ -0,0 +1,331 @@
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: quickref.rb,v 1.14 2005/10/12 14:41:40 austin Exp $
10
+ #++
11
+ require 'pdf/simpletable'
12
+
13
+ # = QuickRef
14
+ # A formatting language to create a quick reference sheet. This is a
15
+ # multi-column page in landscape mode that generally has three or four
16
+ # columns. This format may also be used for brochures, but brochure
17
+ # creation requires a bit of management to create properly.
18
+ #
19
+ # == Reference Sheets
20
+ # A three-column reference sheet is generally in the form of:
21
+ #
22
+ # Page 1:
23
+ # column 1 | column 2 | column 3
24
+ # Page 2:
25
+ # column 4 | column 5 | column 6
26
+ #
27
+ # The formatting language provided in QuickRef is based around this text
28
+ # flow. The title of the quick reference sheet is in column 1. The two
29
+ # pages are intended to be printed on both sides of pieces of paper so
30
+ # that columns 1 and 6 are matched. This will use a Z-fold that places
31
+ # columns 5 and 6 face to face and columns 2 and 3 face to face. In the
32
+ # folded reference sheet, columns 1 and 4 will be facing out.
33
+ #
34
+ # == Brochures
35
+ # In contrast, brochures differ vastly in their design, although the
36
+ # common brochure is also three columns and either follows the same layout
37
+ # as a reference sheet or uses an overlapping fold.
38
+ #
39
+ # When an overlapping fold is used, the title is typically on column 6
40
+ # (assuming a left-to-right reading order). A short summary will appear on
41
+ # column 4. Contact information about the maker of the brochure is
42
+ # typically in column 5. Columns 1, 2, and 3 will contain the main body of
43
+ # the brochure. The brochure will be folded so that columns 2 and 3 are
44
+ # face to face. After this, column 1 will face column 4 (exposed by the
45
+ # first fold). In the folded brochure, columns 5 and 6 are facing out.
46
+ #
47
+ # == Usage
48
+ # qr = PDF::QuickRef.new # 3-column LETTER
49
+ # qr.title "My QuickRef"
50
+ # qr.h1 "H1 Text"
51
+ # qr.lines "Text to put after the header."
52
+ # qr.save_as "MyQuickRef.pdf"
53
+ class PDF::QuickRef
54
+ VERSION = '1.1.4'
55
+
56
+ # Create the quick reference document. +paper+ is passed unchanged to
57
+ # the PDF::Writer.new; the page is always created landscape. Margins
58
+ # are initialized to 18 points. After some additional initialization is
59
+ # performed, the quick reference document is yielded to an optional
60
+ # block for further configuration. All of this is done before the
61
+ # columns are started.
62
+ #
63
+ # After the columns are started, lines will be drawn between column
64
+ # positions.
65
+ def initialize(paper = "LETTER", columns = 3)
66
+ @pdf = PDF::Writer.new(:paper => paper, :orientation => :landscape)
67
+ @pdf.margins_pt 18
68
+ @pdf.y = @pdf.absolute_top_margin
69
+
70
+ @title_font = "Times-Roman"
71
+ @heading_font = "Times-Roman"
72
+ @body_font = "Times-Roman"
73
+ @code_font = "Courier"
74
+ @title_font_size = 14
75
+ @h1_font_size = 11
76
+ @h2_font_size = 9
77
+ @h3_font_size = 8
78
+ @h4_font_size = 7
79
+ @body_font_size = 6
80
+
81
+ @ptab = PDF::SimpleTable.new do |tab|
82
+ tab.column_order.replace %w(one two)
83
+
84
+ tab.font_size = @body_font_size
85
+ tab.show_lines = :none
86
+ tab.show_headings = false
87
+ tab.orientation = :center
88
+ tab.position = :center
89
+ end
90
+ @ltab = PDF::SimpleTable.new do |tab|
91
+ tab.column_order.replace %w(line)
92
+
93
+ tab.font_size = @body_font_size
94
+ tab.show_lines = :none
95
+ tab.show_headings = false
96
+ tab.orientation = :center
97
+ tab.position = :center
98
+ end
99
+
100
+ yield self if block_given?
101
+
102
+ @pdf.start_columns columns
103
+
104
+ @ptab.font_size = @body_font_size
105
+ @ltab.font_size = @body_font_size
106
+
107
+ @ptab.maximum_width = @pdf.column_width - 10
108
+ @ltab.maximum_width = @pdf.column_width - 10
109
+
110
+ # Put lines between the columns.
111
+ all = @pdf.open_object
112
+ @pdf.save_state
113
+ @pdf.stroke_color! Color::RGB::Black
114
+ @pdf.stroke_style PDF::Writer::StrokeStyle::DEFAULT
115
+ (1 .. (columns - 1)).each do |ii|
116
+ x = @pdf.left_margin + (@pdf.column_width * ii)
117
+ x += (@pdf.column_gutter * (ii - 0.5))
118
+ @pdf.line(x, @pdf.page_height - @pdf.top_margin, x, @pdf.bottom_margin)
119
+ @pdf.stroke
120
+ end
121
+ @pdf.restore_state
122
+ @pdf.close_object
123
+ @pdf.add_object(all, :all_pages)
124
+ end
125
+
126
+ # Access to the raw PDF canvas for normal PDF::Writer configuration.
127
+ attr_reader :pdf
128
+
129
+ # The name of the font that will be used for #title text. The default
130
+ # font is Times-Roman.
131
+ attr_accessor :title_font
132
+ # The font encoding for #title text.
133
+ attr_accessor :title_font_encoding
134
+ # The size #title text. The default is 14 points.
135
+ attr_accessor :title_font_size
136
+
137
+ # The name of the font that will be used for #h1, #h2, #h3, and #h4
138
+ # text. The default is Times-Roman.
139
+ attr_accessor :heading_font
140
+ # The font encoding for #h1, #h2, #h3, and #h4 text.
141
+ attr_accessor :heading_font_encoding
142
+ # The size #h1 text. The default is 11 points.
143
+ attr_accessor :h1_font_size
144
+ # The size #h2 text. The default is 9 points.
145
+ attr_accessor :h2_font_size
146
+ # The size #h3 text. The default is 8 points.
147
+ attr_accessor :h3_font_size
148
+ # The size #h4 text. The default is 7 points.
149
+ attr_accessor :h4_font_size
150
+
151
+ # The name of the font that will be used for #body, #lines, and #pairs
152
+ # text. The default is 'Times-Roman'.
153
+ attr_accessor :body_font
154
+ # The font encoding for #body, #lines, and #pairs text.
155
+ attr_accessor :body_font_encoding
156
+ # The name of the font that will be used for #code, #codelines, and
157
+ # #codepairs text; this is generally a fixed-pitch font. The default is
158
+ # 'Courier'.
159
+ attr_accessor :code_font
160
+ # The font encoding for #code, #codelines, and #codepairs text.
161
+ attr_accessor :code_font_encoding
162
+ # The size #body and #code text. The default is 7 points.
163
+ attr_accessor :body_font_size
164
+
165
+ # Creates a two-column zebra-striped table using the #body font. Each
166
+ # line of the text is a separate row; the two columns are separated by
167
+ # tab characters.
168
+ def pairs(text)
169
+ data = text.split($/).map do |line|
170
+ one, two = line.split(/\t/)
171
+ { 'one' => one, 'two' => two }
172
+ end
173
+ @ptab.data.replace data
174
+ @ptab.render_on(@pdf)
175
+ @pdf.text "\n", :font_size => @body_font_size
176
+ end
177
+ # Creates a two-column zebra-striped table using the #code font. Each
178
+ # line of the text is a separate row; the two columns are separated by
179
+ # tab characters.
180
+ def codepairs(text)
181
+ data = text.split($/).map do |line|
182
+ one, two = line.split(/\t/)
183
+ { 'one' => one, 'two' => two }
184
+ end
185
+ @ptab.data.replace data
186
+ use_code_font
187
+ @ptab.render_on(@pdf)
188
+ use_body_font
189
+ @pdf.text "\n", :font_size => @body_font_size
190
+ end
191
+ # Creates a one-column zebra-striped table using the #body font. Each
192
+ # line of the text is a separate row.
193
+ def lines(text)
194
+ data = text.split($/).map { |line| { "line" => line } }
195
+ @ltab.data.replace data
196
+ @ltab.render_on(@pdf)
197
+ @pdf.text "\n", :font_size => @body_font_size
198
+ end
199
+ # Creates a one-column zebra-striped table using the #code font. Each
200
+ # line of the text is a separate row.
201
+ def codelines(text)
202
+ data = text.split($/).map { |line| { "line" => line } }
203
+ @ltab.data.replace data
204
+ use_code_font
205
+ @ltab.render_on(@pdf)
206
+ use_body_font
207
+ @pdf.text "\n", :font_size => @body_font_size
208
+ end
209
+
210
+ # Change the current font to the #title font.
211
+ def use_title_font
212
+ @pdf.select_font @title_font, @title_font_encoding
213
+ end
214
+ # Change the current font to the heading font (used normally by #h1,
215
+ # #h2, #h3, and #h4|).
216
+ def use_heading_font
217
+ @pdf.select_font @heading_font, @heading_font_encoding
218
+ end
219
+ # Change the current font to the #body font.
220
+ def use_body_font
221
+ @pdf.select_font @body_font, @body_font_encoding
222
+ end
223
+ # Change the current font to the #code font.
224
+ def use_code_font
225
+ @pdf.select_font @code_font, @code_font_encoding
226
+ end
227
+
228
+ # Writes the +text+ with the #title_font and #title_font_size centered
229
+ # in the column. After the title has been written, an #hline will be
230
+ # drawn under the title. The font is set to #body_font after the title
231
+ # is drawn.
232
+ def title(text)
233
+ use_title_font
234
+ @pdf.text text, :font_size => @title_font_size, :justification => :center
235
+ use_body_font
236
+ hline
237
+ end
238
+ # Writes the +text+ with the #heading_font and #h1_font_size left
239
+ # justified in the column. The font is set to #body_font after the
240
+ # heading is drawn.
241
+ def h1(text)
242
+ use_heading_font
243
+ @pdf.text text, :font_size => @h1_font_size
244
+ use_body_font
245
+ end
246
+ # Writes the +text+ with the #heading_font and #h2_font_size left
247
+ # justified in the column. The font is set to #body_font after the
248
+ # heading is drawn.
249
+ def h2(text)
250
+ use_heading_font
251
+ @pdf.text "<i>#{text}</i>", :font_size => @h2_font_size
252
+ use_body_font
253
+ end
254
+ # Writes the +text+ with the #heading_font and #h3_font_size left
255
+ # justified in the column. The font is set to #body_font after the
256
+ # heading is drawn.
257
+ def h3(text)
258
+ use_heading_font
259
+ @pdf.text "<i>#{text}</i>", :font_size => @h3_font_size
260
+ use_body_font
261
+ end
262
+ # Writes the +text+ with the #heading_font and #h4_font_size left
263
+ # justified in the column. The font is set to #body_font after the
264
+ # heading is drawn.
265
+ def h4(text)
266
+ use_heading_font
267
+ @pdf.text "<b><i>#{text}</i></b>", :font_size => @h4_font_size
268
+ use_body_font
269
+ end
270
+ # Writes body text. Paragraphs will be reflowed for optimal placement of
271
+ # text. Text separated by two line separators (e.g., \n\n, although the
272
+ # separator is platform dependent). The text will be placed with full
273
+ # justification.
274
+ def body(text)
275
+ # Transform the text a little.
276
+ paras = text.split(%r(#{$/}{2}))
277
+ paras.map! { |para| para.split($/).join(" ").squeeze(" ") }
278
+ text = paras.join("\n\n")
279
+
280
+ @pdf.text "#{text}\n", :font_size => @body_font_size, :justification => :full
281
+ end
282
+ # Writes code text. Newlines and spaces will be preserved.
283
+ def pre(text)
284
+ use_code_font
285
+ @pdf.text "#{text}\n", :font_size => @body_font_size
286
+ use_body_font
287
+ end
288
+
289
+ # Draws a horizontal line with the specified style and colour.
290
+ def hline(style = PDF::Writer::StrokeStyle::DEFAULT,
291
+ color = Color::RGB::Black)
292
+ @pdf.y -= 2.5
293
+ @pdf.save_state
294
+ @pdf.stroke_style style
295
+ @pdf.stroke_color! color
296
+ x0 = @pdf.left_margin
297
+ x1 = @pdf.left_margin + pdf.column_width
298
+ @pdf.line(x0, @pdf.y, x1, @pdf.y)
299
+ @pdf.stroke
300
+ @pdf.restore_state
301
+ @pdf.y -= 2.5
302
+ end
303
+
304
+ # Writes the Quick Reference to disk.
305
+ def save_as(filename)
306
+ @pdf.save_as(filename)
307
+ end
308
+
309
+ # Generates the PDF document as a string.
310
+ def render
311
+ @pdf.render
312
+ end
313
+
314
+ alias to_s render
315
+
316
+ # Creates a QuickRef document and then calls #instance_eval on the
317
+ # document. This allows for a more natural use of the QuickRef class as
318
+ # a DSL for creating these documents.
319
+ #
320
+ # === Using #make
321
+ # PDF::QuickRef.make do # 3-column LETTER
322
+ # title "My QuickRef"
323
+ # h1 "H1 Text"
324
+ # lines "Text to put after the header."
325
+ # save_as "MyQuickRef.pdf"
326
+ # end
327
+ def self.make(*args, &block)
328
+ qr = PDF::QuickRef.new(*args)
329
+ qr.__send__(:instance_eval, &block)
330
+ end
331
+ end
@@ -0,0 +1,947 @@
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.17 2005/10/12 14:41:40 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.1.4'
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
+ 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], line,
650
+ @font_size, just)
651
+ end
652
+ end
653
+ end
654
+
655
+ dy = y + height - pdf.y + @row_gap
656
+ mx = dy - height * (newPage ? 1 : 0) if (dy - height * (newPage ? 1 : 0)) > mx
657
+ end
658
+
659
+ # Set row to leftOvers so that they will be processed onto the
660
+ # new page
661
+ row = leftOvers
662
+
663
+ # Now add the shading underneath
664
+ unless :none == @shade_rows
665
+ pdf.close_object
666
+
667
+ if (cnt % 2).zero?
668
+ pdf.fill_color!(@shade_color)
669
+ pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
670
+ elsif (cnt % 2).nonzero? and :striped == @shade_rows
671
+ pdf.fill_color!(@shade_color2)
672
+ pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
673
+ end
674
+ pdf.reopen_object(tOID)
675
+ end
676
+
677
+ if :inner == @show_lines or :all == @show_lines
678
+ # draw a line on the top of the block
679
+ pdf.save_state
680
+ pdf.stroke_color! @line_color
681
+ if first_line
682
+ pdf.stroke_style @outer_line_style
683
+ first_line = false
684
+ else
685
+ pdf.stroke_style @inner_line_style
686
+ end
687
+ pdf.line(x0 - @gap / 2.0, y + descender + height, x1 - @gap / 2.0, y + descender + height).stroke
688
+ pdf.restore_state
689
+ end
690
+ end
691
+
692
+ y = y - mx + height
693
+ pdf.y = y
694
+ low_y = y if low_y.nil? or y < low_y
695
+
696
+ # checking row split over pages
697
+ unless @split_rows
698
+ if (((pdf.pageset.size != pageStart) or (pdf.columns? and columnStart != pdf.column_number)) and not second_turn)
699
+ # then we need to go back and try that again!
700
+ newPage = second_turn = true
701
+ tg.rewind_transaction(:row)
702
+ row = row_orig
703
+ low_y = y = y_orig
704
+ y0 = y0_orig
705
+ y1 = y1_orig
706
+ ok = false
707
+
708
+ dm = pdf.absolute_left_margin - base_left_margin
709
+ base_pos.each { |k, v| pos[k] = v + dm }
710
+ x0 = base_x0 + dm
711
+ x1 = base_x1 + dm
712
+ else
713
+ tg.commit_transaction(:row)
714
+ ok = true
715
+ end
716
+ else
717
+ ok = true # don't go 'round the loop if splitting rows is allowed
718
+ end
719
+ end
720
+
721
+ if abortTable
722
+ # abort_transaction if not ok only the outer transaction should
723
+ # be operational.
724
+ tg.rewind_transaction(:table)
725
+ pdf.start_new_page
726
+ # fix a bug where a moved table will take up the whole page.
727
+ low_y = nil
728
+ pdf.save_state
729
+ break
730
+ end
731
+ end
732
+ end
733
+
734
+ if low_y <= y
735
+ y2 = low_y + descender
736
+ else
737
+ y2 = y + descender
738
+ end
739
+
740
+ unless :none == @show_lines
741
+ y0 = y1 unless @show_headings
742
+
743
+ __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2, @line_color,
744
+ @inner_line_style, @outer_line_style, @show_lines)
745
+ end
746
+
747
+ # close the object for drawing the text on top
748
+ unless :none == @shade_rows
749
+ pdf.close_object
750
+ pdf.restore_state
751
+ end
752
+
753
+ pdf.y = low_y
754
+
755
+ # Table has been put on the page, the rows guarded as required; commit.
756
+ tg.commit_transaction(:table)
757
+
758
+ y
759
+ rescue Exception => ex
760
+ begin
761
+ tg.abort_transaction(:table) if tg.transaction_open?
762
+ rescue
763
+ nil
764
+ end
765
+ raise ex
766
+ end
767
+
768
+ WIDTH_FACTOR = 1.01
769
+
770
+ # Find the maximum widths of the text within each column. Default to
771
+ # zero.
772
+ def __find_table_max_width__(pdf)
773
+ max_width = PDF::Writer::OHash.new(-1)
774
+
775
+ # Find the maximum cell widths based on the data and the headings.
776
+ # Passing through the data multiple times is unavoidable as we must do
777
+ # some analysis first.
778
+ @data.each do |row|
779
+ @cols.each do |name, column|
780
+ w = pdf.text_width(row[name].to_s, @font_size)
781
+ w *= WIDTH_FACTOR
782
+
783
+ max_width[name] = w if w > max_width[name]
784
+ end
785
+ end
786
+
787
+ @cols.each do |name, column|
788
+ title = column.heading.title if column.heading
789
+ title ||= column.name
790
+ w = pdf.text_width(title, @heading_font_size)
791
+ w *= WIDTH_FACTOR
792
+ max_width[name] = w if w > max_width[name]
793
+ end
794
+ max_width
795
+ end
796
+ private :__find_table_max_width__
797
+
798
+ # Calculate the start positions of each of the columns. This is based
799
+ # on max_width, but may be modified with column options.
800
+ def __find_table_positions__(pdf, max_width)
801
+ pos = PDF::Writer::OHash.new
802
+ x = t = adjustment_width = set_width = 0
803
+
804
+ max_width.each do |name, w|
805
+ pos[name] = t
806
+ # If the column width has been specified then set that here, also
807
+ # total the width avaliable for adjustment.
808
+ if not @cols[name].nil? and
809
+ not @cols[name].width.nil? and
810
+ @cols[name].width > 0
811
+ t += @cols[name].width
812
+ max_width[name] = @cols[name].width - @gap
813
+ set_width += @cols[name].width
814
+ else
815
+ t += w + @gap
816
+ adjustment_width += w
817
+ set_width += @gap
818
+ end
819
+ end
820
+ pos[:__last_column__] = t
821
+
822
+ [pos, t, x, adjustment_width, set_width]
823
+ end
824
+ private :__find_table_positions__
825
+
826
+ # Uses ezText to add the text, and returns the height taken by the
827
+ # largest heading. This page will move the headings to a new page if
828
+ # they will not fit completely on this one transaction support will be
829
+ # used to implement this.
830
+ def __table_column_headings__(pdf, pos, max_width, height, descender, gap, size, y)
831
+ mx = second_go = 0
832
+ start_page = pdf.pageset.size
833
+
834
+ # y is the position at which the top of the table should start, so the
835
+ # base of the first text, is y-height-gap-descender, but ezText starts
836
+ # by dropping height.
837
+
838
+ # The return from this function is the total cell height, including
839
+ # gaps, and y is adjusted to be the postion of the bottom line.
840
+ tg = Transaction::Simple::Group.new(pdf, self)
841
+ tg.start_transaction(:column_headings)
842
+
843
+ ok = false
844
+ y -= gap
845
+ loop do
846
+ break if ok
847
+ @cols.each do |name, column|
848
+ pdf.pointer = y
849
+
850
+ if column.heading
851
+ justification = column.heading.justification
852
+ bold = column.heading.bold
853
+ title = column.heading.title
854
+ end
855
+
856
+ justification ||= :left
857
+ bold ||= @bold_headings
858
+ title ||= column.name
859
+
860
+ title = "<b>#{title}</b>" if bold
861
+
862
+ pdf.text(title, :font_size => size, :absolute_left => pos[name],
863
+ :absolute_right => (max_width[name] + pos[name]),
864
+ :justification => justification)
865
+ dy = y - pdf.y
866
+ mx = dy if dy > mx
867
+ end
868
+
869
+ y -= (mx + gap) - descender # y = y - mx - gap + descender
870
+
871
+ # If this has been moved to a new page, then abort the transaction;
872
+ # move to a new page, and put it there. Do not check on the second
873
+ # time around to avoid an infinite loop.
874
+ if (pdf.pageset.size != start_page and not second_go)
875
+ tg.rewind_transaction(:column_headings)
876
+
877
+ pdf.start_new_page
878
+ save_state
879
+ y = @y - gap - descender
880
+ ok = false
881
+ second_go = true
882
+ mx = 0
883
+ else
884
+ tg.commit_transaction(:column_headings)
885
+ ok = true
886
+ end
887
+ end
888
+
889
+ return [mx + gap * 2 - descender, y]
890
+ rescue Exception => ex
891
+ begin
892
+ tg.abort_transaction(:column_headings) if tg.transaction_open?(:column_headings)
893
+ rescue
894
+ nil
895
+ end
896
+ raise ex
897
+ end
898
+ private :__table_column_headings__
899
+
900
+ def __table_draw_lines__(pdf, pos, gap, x0, x1, y0, y1, y2, col, inner, outer, opt = :outer)
901
+ x0 = 1000
902
+ x1 = 0
903
+
904
+ pdf.stroke_color(col)
905
+
906
+ cnt = 0
907
+ n = pos.size
908
+
909
+ pos.each do |name, x|
910
+ cnt += 1
911
+
912
+ if (cnt == 1 or cnt == n)
913
+ pdf.stroke_style outer
914
+ else
915
+ pdf.stroke_style inner
916
+ end
917
+
918
+ pdf.line(x - gap / 2.0, y0, x - gap / 2.0, y2).stroke
919
+ x1 = x if x > x1
920
+ x0 = x if x < x0
921
+ end
922
+
923
+ pdf.stroke_style outer
924
+
925
+ pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y0,
926
+ x1 - (gap / 2.0) + (outer.width / 2.0), y0).stroke
927
+
928
+ # Only do the second line if it is different than the first AND each
929
+ # row does not have a line on it.
930
+ if y0 != y1 and @show_lines == :outer
931
+ pdf.line(x0 - gap / 2.0, y1, x1 - gap / 2.0, y1).stroke
932
+ end
933
+ pdf.line(x0 - (gap / 2.0) - (outer.width / 2.0), y2,
934
+ x1 - (gap / 2.0) + (outer.width / 2.0), y2).stroke
935
+ end
936
+ private :__table_draw_lines__
937
+
938
+ def __open_new_object__(pdf)
939
+ pdf.save_state
940
+ tOID = pdf.open_object
941
+ pdf.close_object
942
+ pdf.add_object(tOID)
943
+ pdf.reopen_object(tOID)
944
+ tOID
945
+ end
946
+ private :__open_new_object__
947
+ end