pdf-labels 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/LICENCE +38 -0
- data/Manifest.txt +141 -0
- data/README.txt +72 -0
- data/Rakefile +30 -0
- data/lib/alias.rb +8 -0
- data/lib/glabel_template.rb +36 -0
- data/lib/label.rb +52 -0
- data/lib/layout.rb +13 -0
- data/lib/length_node.rb +47 -0
- data/lib/markup.rb +25 -0
- data/lib/pdf_label_page.rb +171 -0
- data/lib/pdf_labels.rb +6 -0
- data/lib/template.rb +37 -0
- data/templates/avery-iso-templates.xml +222 -0
- data/templates/avery-other-templates.xml +21 -0
- data/templates/avery-us-templates.xml +599 -0
- data/templates/glabels-2.0.dtd +329 -0
- data/templates/misc-iso-templates.xml +434 -0
- data/templates/misc-other-templates.xml +21 -0
- data/templates/misc-us-templates.xml +183 -0
- data/templates/paper-sizes.xml +37 -0
- data/templates/zweckform-iso-templates.xml +197 -0
- data/test/test_pdf_label_page.rb +91 -0
- data/vendor/color.rb +87 -0
- data/vendor/color/cmyk.rb +182 -0
- data/vendor/color/css.rb +27 -0
- data/vendor/color/grayscale.rb +135 -0
- data/vendor/color/hsl.rb +130 -0
- data/vendor/color/palette.rb +15 -0
- data/vendor/color/palette/gimp.rb +107 -0
- data/vendor/color/palette/monocontrast.rb +180 -0
- data/vendor/color/rgb-colors.rb +189 -0
- data/vendor/color/rgb.rb +311 -0
- data/vendor/color/rgb/metallic.rb +28 -0
- data/vendor/color/yiq.rb +78 -0
- data/vendor/pdf/charts.rb +13 -0
- data/vendor/pdf/charts/stddev.rb +433 -0
- data/vendor/pdf/grid.rb +135 -0
- data/vendor/pdf/math.rb +108 -0
- data/vendor/pdf/pagenumbers.rb +288 -0
- data/vendor/pdf/quickref.rb +331 -0
- data/vendor/pdf/simpletable.rb +947 -0
- data/vendor/pdf/techbook.rb +901 -0
- data/vendor/pdf/writer.rb +2801 -0
- data/vendor/pdf/writer/arc4.rb +63 -0
- data/vendor/pdf/writer/fontmetrics.rb +202 -0
- data/vendor/pdf/writer/fonts/Courier-Bold.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier-Oblique.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier.afm +342 -0
- data/vendor/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
- data/vendor/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/vendor/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
- data/vendor/pdf/writer/fonts/Helvetica.afm +3051 -0
- data/vendor/pdf/writer/fonts/Symbol.afm +213 -0
- data/vendor/pdf/writer/fonts/Times-Bold.afm +2588 -0
- data/vendor/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
- data/vendor/pdf/writer/fonts/Times-Italic.afm +2667 -0
- data/vendor/pdf/writer/fonts/Times-Roman.afm +2419 -0
- data/vendor/pdf/writer/fonts/ZapfDingbats.afm +225 -0
- data/vendor/pdf/writer/graphics.rb +813 -0
- data/vendor/pdf/writer/graphics/imageinfo.rb +365 -0
- data/vendor/pdf/writer/lang.rb +44 -0
- data/vendor/pdf/writer/lang/en.rb +104 -0
- data/vendor/pdf/writer/object.rb +23 -0
- data/vendor/pdf/writer/object/action.rb +40 -0
- data/vendor/pdf/writer/object/annotation.rb +42 -0
- data/vendor/pdf/writer/object/catalog.rb +39 -0
- data/vendor/pdf/writer/object/contents.rb +69 -0
- data/vendor/pdf/writer/object/destination.rb +40 -0
- data/vendor/pdf/writer/object/encryption.rb +53 -0
- data/vendor/pdf/writer/object/font.rb +68 -0
- data/vendor/pdf/writer/object/fontdescriptor.rb +34 -0
- data/vendor/pdf/writer/object/fontencoding.rb +40 -0
- data/vendor/pdf/writer/object/image.rb +308 -0
- data/vendor/pdf/writer/object/info.rb +79 -0
- data/vendor/pdf/writer/object/outline.rb +30 -0
- data/vendor/pdf/writer/object/outlines.rb +30 -0
- data/vendor/pdf/writer/object/page.rb +195 -0
- data/vendor/pdf/writer/object/pages.rb +115 -0
- data/vendor/pdf/writer/object/procset.rb +46 -0
- data/vendor/pdf/writer/object/viewerpreferences.rb +74 -0
- data/vendor/pdf/writer/ohash.rb +58 -0
- data/vendor/pdf/writer/oreader.rb +25 -0
- data/vendor/pdf/writer/state.rb +48 -0
- data/vendor/pdf/writer/strokestyle.rb +140 -0
- data/vendor/transaction/simple.rb +693 -0
- data/vendor/transaction/simple/group.rb +133 -0
- data/vendor/transaction/simple/threadsafe.rb +52 -0
- data/vendor/transaction/simple/threadsafe/group.rb +23 -0
- data/vendor/xml-mapping/ChangeLog +128 -0
- data/vendor/xml-mapping/LICENSE +56 -0
- data/vendor/xml-mapping/README +386 -0
- data/vendor/xml-mapping/README_XPATH +175 -0
- data/vendor/xml-mapping/Rakefile +214 -0
- data/vendor/xml-mapping/TODO.txt +32 -0
- data/vendor/xml-mapping/doc/xpath_impl_notes.txt +119 -0
- data/vendor/xml-mapping/examples/company.rb +34 -0
- data/vendor/xml-mapping/examples/company.xml +26 -0
- data/vendor/xml-mapping/examples/company_usage.intin.rb +19 -0
- data/vendor/xml-mapping/examples/company_usage.intout +39 -0
- data/vendor/xml-mapping/examples/order.rb +61 -0
- data/vendor/xml-mapping/examples/order.xml +54 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced.rb +7 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced.xml +9 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intin.rb +12 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intout +16 -0
- data/vendor/xml-mapping/examples/order_usage.intin.rb +73 -0
- data/vendor/xml-mapping/examples/order_usage.intout +147 -0
- data/vendor/xml-mapping/examples/time_augm.intin.rb +19 -0
- data/vendor/xml-mapping/examples/time_augm.intout +23 -0
- data/vendor/xml-mapping/examples/time_node.rb +27 -0
- data/vendor/xml-mapping/examples/xpath_create_new.intin.rb +85 -0
- data/vendor/xml-mapping/examples/xpath_create_new.intout +181 -0
- data/vendor/xml-mapping/examples/xpath_docvsroot.intin.rb +30 -0
- data/vendor/xml-mapping/examples/xpath_docvsroot.intout +34 -0
- data/vendor/xml-mapping/examples/xpath_ensure_created.intin.rb +62 -0
- data/vendor/xml-mapping/examples/xpath_ensure_created.intout +114 -0
- data/vendor/xml-mapping/examples/xpath_pathological.intin.rb +42 -0
- data/vendor/xml-mapping/examples/xpath_pathological.intout +56 -0
- data/vendor/xml-mapping/examples/xpath_usage.intin.rb +51 -0
- data/vendor/xml-mapping/examples/xpath_usage.intout +57 -0
- data/vendor/xml-mapping/install.rb +40 -0
- data/vendor/xml-mapping/lib/xml/mapping.rb +14 -0
- data/vendor/xml-mapping/lib/xml/mapping/base.rb +571 -0
- data/vendor/xml-mapping/lib/xml/mapping/standard_nodes.rb +343 -0
- data/vendor/xml-mapping/lib/xml/mapping/version.rb +8 -0
- data/vendor/xml-mapping/lib/xml/xxpath.rb +354 -0
- data/vendor/xml-mapping/test/all_tests.rb +6 -0
- data/vendor/xml-mapping/test/company.rb +56 -0
- data/vendor/xml-mapping/test/documents_folders.rb +33 -0
- data/vendor/xml-mapping/test/fixtures/bookmarks1.xml +24 -0
- data/vendor/xml-mapping/test/fixtures/company1.xml +85 -0
- data/vendor/xml-mapping/test/fixtures/documents_folders.xml +71 -0
- data/vendor/xml-mapping/test/fixtures/documents_folders2.xml +30 -0
- data/vendor/xml-mapping/test/multiple_mappings.rb +80 -0
- data/vendor/xml-mapping/test/tests_init.rb +2 -0
- data/vendor/xml-mapping/test/xml_mapping_adv_test.rb +84 -0
- data/vendor/xml-mapping/test/xml_mapping_test.rb +201 -0
- data/vendor/xml-mapping/test/xpath_test.rb +273 -0
- 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
|