pdf-labels 1.0.0

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