jsanders-ruport 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +48 -0
- data/LICENSE +59 -0
- data/README +114 -0
- data/Rakefile +93 -0
- data/examples/RWEmerson.jpg +0 -0
- data/examples/anon.rb +43 -0
- data/examples/btree/commaleon/commaleon.rb +263 -0
- data/examples/btree/commaleon/sample_data/ticket_count.csv +124 -0
- data/examples/btree/commaleon/sample_data/ticket_count2.csv +119 -0
- data/examples/centered_pdf_text_box.rb +83 -0
- data/examples/data/tattle.dump +82 -0
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +61 -0
- data/examples/pdf_report_with_common_base.rb +72 -0
- data/examples/png_embed.rb +54 -0
- data/examples/roadmap.png +0 -0
- data/examples/row_renderer.rb +39 -0
- data/examples/simple_pdf_lines.rb +25 -0
- data/examples/simple_templating_example.rb +34 -0
- data/examples/tattle_ruby_version.rb +39 -0
- data/examples/tattle_rubygems_version.rb +37 -0
- data/examples/trac_ticket_status.rb +59 -0
- data/lib/ruport.rb +127 -0
- data/lib/ruport/controller.rb +616 -0
- data/lib/ruport/controller/grouping.rb +71 -0
- data/lib/ruport/controller/table.rb +54 -0
- data/lib/ruport/data.rb +4 -0
- data/lib/ruport/data/feeder.rb +111 -0
- data/lib/ruport/data/grouping.rb +399 -0
- data/lib/ruport/data/record.rb +297 -0
- data/lib/ruport/data/table.rb +950 -0
- data/lib/ruport/extensions.rb +4 -0
- data/lib/ruport/formatter.rb +254 -0
- data/lib/ruport/formatter/csv.rb +149 -0
- data/lib/ruport/formatter/html.rb +161 -0
- data/lib/ruport/formatter/pdf.rb +591 -0
- data/lib/ruport/formatter/template.rb +187 -0
- data/lib/ruport/formatter/text.rb +231 -0
- data/lib/uport.rb +1 -0
- data/test/controller_test.rb +743 -0
- data/test/csv_formatter_test.rb +164 -0
- data/test/data_feeder_test.rb +88 -0
- data/test/grouping_test.rb +410 -0
- data/test/helpers.rb +11 -0
- data/test/html_formatter_test.rb +201 -0
- data/test/pdf_formatter_test.rb +354 -0
- data/test/record_test.rb +332 -0
- data/test/samples/addressbook.csv +6 -0
- data/test/samples/data.csv +3 -0
- data/test/samples/data.tsv +3 -0
- data/test/samples/dates.csv +1409 -0
- data/test/samples/erb_test.sql +1 -0
- data/test/samples/query_test.sql +1 -0
- data/test/samples/ruport_test.sql +8 -0
- data/test/samples/test.sql +2 -0
- data/test/samples/test.yaml +3 -0
- data/test/samples/ticket_count.csv +124 -0
- data/test/table_pivot_test.rb +134 -0
- data/test/table_test.rb +838 -0
- data/test/template_test.rb +48 -0
- data/test/text_formatter_test.rb +258 -0
- data/util/bench/data/record/bench_as_vs_to.rb +18 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +176 -0
@@ -0,0 +1,591 @@
|
|
1
|
+
# Ruport : Extensible Reporting System
|
2
|
+
#
|
3
|
+
# formatter/pdf.rb provides text formatting for Ruport.
|
4
|
+
#
|
5
|
+
# Created by Gregory Brown, February 2006
|
6
|
+
# Extended by James Healy, Fall 2006
|
7
|
+
# Copyright (C) 2006-2007 Gregory Brown / James Healy, All Rights Reserved.
|
8
|
+
#
|
9
|
+
# Initially inspired by some ideas and code from Simon Claret,
|
10
|
+
# with many improvements from James Healy and Michael Milner over time.
|
11
|
+
#
|
12
|
+
# This is free software distributed under the same terms as Ruby 1.8
|
13
|
+
# See LICENSE and COPYING for details.
|
14
|
+
#
|
15
|
+
module Ruport
|
16
|
+
|
17
|
+
# This class provides PDF output for Ruport's Table, Group, and Grouping
|
18
|
+
# controllers. It wraps Austin Ziegler's PDF::Writer to provide a higher
|
19
|
+
# level interface and provides a number of helpers designed to make
|
20
|
+
# generating PDF reports much easier. You will typically want to build
|
21
|
+
# subclasses of this formatter to customize it as needed.
|
22
|
+
#
|
23
|
+
# Many methods forward options to PDF::Writer, so you may wish to consult
|
24
|
+
# its API docs.
|
25
|
+
#
|
26
|
+
# === Rendering Options
|
27
|
+
# General:
|
28
|
+
# * paper_size #=> "LETTER"
|
29
|
+
# * paper_orientation #=> :portrait
|
30
|
+
#
|
31
|
+
# Text:
|
32
|
+
# * text_format (sets options to be passed to add_text by default)
|
33
|
+
#
|
34
|
+
# Table:
|
35
|
+
# * table_format (a hash that can take any of the options available
|
36
|
+
# to PDF::SimpleTable)
|
37
|
+
# * table_format[:maximum_width] #=> 500
|
38
|
+
#
|
39
|
+
# Grouping:
|
40
|
+
# * style (:inline,:justified,:separated,:offset)
|
41
|
+
#
|
42
|
+
class Formatter::PDF < Formatter
|
43
|
+
|
44
|
+
module PDFWriterProxy #:nodoc:
|
45
|
+
def method_missing(id,*args)
|
46
|
+
super(id,*args)
|
47
|
+
rescue
|
48
|
+
pdf_writer.send(id,*args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
renders :pdf, :for => [ Controller::Row, Controller::Table,
|
53
|
+
Controller::Group, Controller::Grouping ]
|
54
|
+
|
55
|
+
attr_writer :pdf_writer
|
56
|
+
|
57
|
+
|
58
|
+
# If you use this macro in your formatter, Ruport will automatically forward
|
59
|
+
# calls to the underlying PDF::Writer, for any methods that are not wrapped
|
60
|
+
# or redefined.
|
61
|
+
def self.proxy_to_pdf_writer
|
62
|
+
include PDFWriterProxy
|
63
|
+
end
|
64
|
+
|
65
|
+
save_as_binary_file
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
Ruport.quiet do
|
69
|
+
require "pdf/writer"
|
70
|
+
require "pdf/simpletable"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Hook for setting available options using a template. See the template
|
75
|
+
# documentation for the available options and their format.
|
76
|
+
def apply_template
|
77
|
+
apply_page_format_template(template.page)
|
78
|
+
apply_text_format_template(template.text)
|
79
|
+
apply_table_format_template(template.table)
|
80
|
+
apply_column_format_template(template.column)
|
81
|
+
apply_heading_format_template(template.heading)
|
82
|
+
apply_grouping_format_template(template.grouping)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the current PDF::Writer object or creates a new one if it has not
|
86
|
+
# been set yet.
|
87
|
+
#
|
88
|
+
def pdf_writer
|
89
|
+
@pdf_writer ||= options.formatter ||
|
90
|
+
::PDF::Writer.new( :paper => options.paper_size || "LETTER",
|
91
|
+
:orientation => options.paper_orientation || :portrait)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Calls the draw_table method.
|
95
|
+
#
|
96
|
+
def build_table_body
|
97
|
+
draw_table(data)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Appends the results of PDF::Writer#render to output for your
|
101
|
+
# <tt>pdf_writer</tt> object.
|
102
|
+
#
|
103
|
+
def finalize_table
|
104
|
+
render_pdf unless options.skip_finalize_table
|
105
|
+
end
|
106
|
+
|
107
|
+
# Generates a header with the group name for Controller::Group.
|
108
|
+
def build_group_header
|
109
|
+
pad(10) { add_text data.name.to_s, :justification => :center }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Renders the group as a table for Controller::Group.
|
113
|
+
def build_group_body
|
114
|
+
render_table data, options.to_hash.merge(:formatter => pdf_writer)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Determines which style to use and renders the main body for
|
118
|
+
# Controller::Grouping.
|
119
|
+
def build_grouping_body
|
120
|
+
case options.style
|
121
|
+
when :inline
|
122
|
+
render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer,
|
123
|
+
:skip_finalize_table => true))
|
124
|
+
when :justified, :separated
|
125
|
+
render_justified_or_separated_grouping
|
126
|
+
when :offset
|
127
|
+
render_offset_grouping
|
128
|
+
else
|
129
|
+
raise NotImplementedError, "Unknown style"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Calls <tt>render_pdf</tt>.
|
134
|
+
def finalize_grouping
|
135
|
+
render_pdf
|
136
|
+
end
|
137
|
+
|
138
|
+
# Call PDF::Writer#text with the given arguments, using
|
139
|
+
# <tt>text_format</tt> defaults, if they are defined.
|
140
|
+
#
|
141
|
+
# Example:
|
142
|
+
#
|
143
|
+
# options.text_format { :font_size => 14 }
|
144
|
+
#
|
145
|
+
# add_text("Hello Joe") #renders at 14pt
|
146
|
+
# add_text("Hello Mike",:font_size => 16) # renders at 16pt
|
147
|
+
def add_text(text, format_opts={})
|
148
|
+
format_opts = options.text_format.merge(format_opts) if options.text_format
|
149
|
+
pdf_writer.text(text, format_opts)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Calls PDF::Writer#render and appends to <tt>output</tt>.
|
153
|
+
def render_pdf
|
154
|
+
output << pdf_writer.render
|
155
|
+
end
|
156
|
+
|
157
|
+
# - If the image is bigger than the box, it will be scaled down until
|
158
|
+
# it fits.
|
159
|
+
# - If the image is smaller than the box, it won't be resized.
|
160
|
+
#
|
161
|
+
# options:
|
162
|
+
# - :x: left bound of box
|
163
|
+
# - :y: bottom bound of box
|
164
|
+
# - :width: width of box
|
165
|
+
# - :height: height of box
|
166
|
+
#
|
167
|
+
def center_image_in_box(path, image_opts={})
|
168
|
+
x = image_opts[:x]
|
169
|
+
y = image_opts[:y]
|
170
|
+
width = image_opts[:width]
|
171
|
+
height = image_opts[:height]
|
172
|
+
info = ::PDF::Writer::Graphics::ImageInfo.new(File.open(path, "rb"))
|
173
|
+
|
174
|
+
# reduce the size of the image until it fits into the requested box
|
175
|
+
img_width, img_height =
|
176
|
+
fit_image_in_box(info.width,width,info.height,height)
|
177
|
+
|
178
|
+
# if the image is smaller than the box, calculate the white space buffer
|
179
|
+
x, y = add_white_space(x,y,img_width,width,img_height,height)
|
180
|
+
|
181
|
+
pdf_writer.add_image_from_file(path, x, y, img_width, img_height)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Draws some text on the canvas, surrounded by a box with rounded corners.
|
185
|
+
#
|
186
|
+
# Yields an OpenStruct which options can be defined on.
|
187
|
+
#
|
188
|
+
# Example:
|
189
|
+
#
|
190
|
+
# rounded_text_box(options.text) do |o|
|
191
|
+
# o.radius = 5
|
192
|
+
# o.width = options.width || 400
|
193
|
+
# o.height = options.height || 130
|
194
|
+
# o.font_size = options.font_size || 12
|
195
|
+
# o.heading = options.heading
|
196
|
+
#
|
197
|
+
# o.x = pdf_writer.absolute_x_middle - o.width/2
|
198
|
+
# o.y = 300
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
def rounded_text_box(text)
|
202
|
+
opts = OpenStruct.new
|
203
|
+
yield(opts)
|
204
|
+
|
205
|
+
resize_text_to_box(text, opts)
|
206
|
+
|
207
|
+
pdf_writer.save_state
|
208
|
+
draw_box(opts.x, opts.y, opts.width, opts.height, opts.radius,
|
209
|
+
opts.fill_color, opts.stroke_color)
|
210
|
+
add_text_with_bottom_border(opts.heading, opts.x, opts.y,
|
211
|
+
opts.width, opts.font_size) if opts.heading
|
212
|
+
pdf_writer.restore_state
|
213
|
+
|
214
|
+
start_position = opts.heading ? opts.y - 20 : opts.y
|
215
|
+
draw_text(text, :y => start_position,
|
216
|
+
:left => opts.x,
|
217
|
+
:right => opts.x + opts.width,
|
218
|
+
:justification => opts.justification || :center,
|
219
|
+
:font_size => opts.font_size)
|
220
|
+
move_cursor_to(opts.y - opts.height)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Adds n to pdf_writer.y, moving the vertical drawing position in the
|
224
|
+
# document.
|
225
|
+
def move_cursor(n)
|
226
|
+
pdf_writer.y += n
|
227
|
+
end
|
228
|
+
|
229
|
+
# Moves the cursor to a specific y coordinate in the document.
|
230
|
+
def move_cursor_to(n)
|
231
|
+
pdf_writer.y = n
|
232
|
+
end
|
233
|
+
|
234
|
+
# Moves the vertical drawing position in the document upwards by n.
|
235
|
+
def move_up(n)
|
236
|
+
pdf_writer.y += n
|
237
|
+
end
|
238
|
+
|
239
|
+
def move_down(n)
|
240
|
+
pdf_writer.y -= n
|
241
|
+
end
|
242
|
+
|
243
|
+
# Adds a specified amount of whitespace above and below the code
|
244
|
+
# in your block. For example, if you want to surround the top and
|
245
|
+
# bottom of a line of text with 5 pixels of whitespace:
|
246
|
+
#
|
247
|
+
# pad(5) { add_text "This will be padded top and bottom" }
|
248
|
+
def pad(y,&block)
|
249
|
+
move_cursor(-y)
|
250
|
+
block.call
|
251
|
+
move_cursor(-y)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Adds a specified amount of whitespace above the code in your block.
|
255
|
+
# For example, if you want to add a 10 pixel buffer to the top of a
|
256
|
+
# line of text:
|
257
|
+
#
|
258
|
+
# pad_top(10) { add_text "This will be padded on top" }
|
259
|
+
def pad_top(y,&block)
|
260
|
+
move_cursor(-y)
|
261
|
+
block.call
|
262
|
+
end
|
263
|
+
|
264
|
+
# Adds a specified amount of whitespace below the code in your block.
|
265
|
+
# For example, if you want to add a 10 pixel buffer to the bottom of a
|
266
|
+
# line of text:
|
267
|
+
#
|
268
|
+
# pad_bottom(10) { add_text "This will be padded on bottom" }
|
269
|
+
def pad_bottom(y,&block)
|
270
|
+
block.call
|
271
|
+
move_cursor(-y)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Draws a PDF::SimpleTable using the given data (usually a Data::Table).
|
275
|
+
# Takes all the options you can set on a PDF::SimpleTable object,
|
276
|
+
# see the PDF::Writer API docs for details, or check our quick reference
|
277
|
+
# at:
|
278
|
+
#
|
279
|
+
# http://stonecode.svnrepository.com/ruport/trac.cgi/wiki/PdfWriterQuickRef
|
280
|
+
def draw_table(table_data, format_opts={})
|
281
|
+
m = "PDF Formatter requires column_names to be defined"
|
282
|
+
raise FormatterError, m if table_data.column_names.empty?
|
283
|
+
|
284
|
+
table_data.rename_columns { |c| c.to_s }
|
285
|
+
|
286
|
+
if options.table_format
|
287
|
+
format_opts =
|
288
|
+
Marshal.load(Marshal.dump(options.table_format.merge(format_opts)))
|
289
|
+
end
|
290
|
+
|
291
|
+
old = pdf_writer.font_size
|
292
|
+
|
293
|
+
::PDF::SimpleTable.new do |table|
|
294
|
+
table.maximum_width = 500
|
295
|
+
table.column_order = table_data.column_names
|
296
|
+
table.data = table_data
|
297
|
+
table.data = [{}] if table.data.empty?
|
298
|
+
apply_pdf_table_column_opts(table,table_data,format_opts)
|
299
|
+
|
300
|
+
format_opts.each {|k,v| table.send("#{k}=", v) }
|
301
|
+
table.render_on(pdf_writer)
|
302
|
+
end
|
303
|
+
|
304
|
+
pdf_writer.font_size = old
|
305
|
+
end
|
306
|
+
|
307
|
+
# This module provides tools to simplify some common drawing operations.
|
308
|
+
# It is included by default in the PDF formatter.
|
309
|
+
#
|
310
|
+
module DrawingHelpers
|
311
|
+
|
312
|
+
# Draws a horizontal line from x1 to x2
|
313
|
+
def horizontal_line(x1,x2)
|
314
|
+
pdf_writer.line(x1,cursor,x2,cursor)
|
315
|
+
pdf_writer.stroke
|
316
|
+
end
|
317
|
+
|
318
|
+
# Draws a horizontal line from left_boundary to right_boundary
|
319
|
+
def horizontal_rule
|
320
|
+
horizontal_line(left_boundary,right_boundary)
|
321
|
+
end
|
322
|
+
|
323
|
+
alias_method :hr, :horizontal_rule
|
324
|
+
|
325
|
+
# Draws a vertical line at x from y1 to y2
|
326
|
+
def vertical_line_at(x,y1,y2)
|
327
|
+
pdf_writer.line(x,y1,x,y2)
|
328
|
+
pdf_writer.stroke
|
329
|
+
end
|
330
|
+
|
331
|
+
# Alias for PDF::Writer#absolute_left_margin
|
332
|
+
def left_boundary
|
333
|
+
pdf_writer.absolute_left_margin
|
334
|
+
end
|
335
|
+
|
336
|
+
# Alias for PDF::Writer#absolute_right_margin
|
337
|
+
def right_boundary
|
338
|
+
pdf_writer.absolute_right_margin
|
339
|
+
end
|
340
|
+
|
341
|
+
# Alias for PDF::Writer#absolute_top_margin
|
342
|
+
def top_boundary
|
343
|
+
pdf_writer.absolute_top_margin
|
344
|
+
end
|
345
|
+
|
346
|
+
# Alias for PDF::Writer#absolute_bottom_margin
|
347
|
+
def bottom_boundary
|
348
|
+
pdf_writer.absolute_bottom_margin
|
349
|
+
end
|
350
|
+
|
351
|
+
# Alias for PDF::Writer#y
|
352
|
+
def cursor
|
353
|
+
pdf_writer.y
|
354
|
+
end
|
355
|
+
|
356
|
+
# Draws text at an absolute location, defined by
|
357
|
+
# :y, :x1|:left, :x2|:right
|
358
|
+
#
|
359
|
+
# All options to add_text are also supported.
|
360
|
+
def draw_text(text,text_opts)
|
361
|
+
ypos = cursor
|
362
|
+
move_cursor_to(text_opts[:y]) if text_opts[:y]
|
363
|
+
add_text(text,
|
364
|
+
text_opts.merge(:absolute_left => text_opts[:x1] || text_opts[:left],
|
365
|
+
:absolute_right => text_opts[:x2] || text_opts[:right]))
|
366
|
+
move_cursor_to(ypos)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Draws text at an absolute location, defined by
|
370
|
+
# :y, :x1|:left
|
371
|
+
#
|
372
|
+
# The x position defaults to the left margin and the
|
373
|
+
# y position defaults to the current cursor location.
|
374
|
+
#
|
375
|
+
# Uses PDF::Writer#add_text, so it will ignore any options not supported
|
376
|
+
# by that method.
|
377
|
+
def draw_text!(text,text_opts)
|
378
|
+
ypos = cursor
|
379
|
+
pdf_writer.add_text(text_opts[:x1] || text_opts[:left] || left_boundary,
|
380
|
+
text_opts[:y] || ypos,
|
381
|
+
text,
|
382
|
+
text_opts[:font_size],
|
383
|
+
text_opts[:angle] || 0)
|
384
|
+
move_cursor_to(ypos)
|
385
|
+
end
|
386
|
+
|
387
|
+
def finalize
|
388
|
+
render_pdf
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
include DrawingHelpers
|
393
|
+
|
394
|
+
private
|
395
|
+
|
396
|
+
def apply_pdf_table_column_opts(table,table_data,format_opts)
|
397
|
+
column_opts = format_opts.delete(:column_options)
|
398
|
+
|
399
|
+
if column_opts
|
400
|
+
heading_opts = column_opts.delete(:heading)
|
401
|
+
if column_opts[:justification]
|
402
|
+
heading_opts ||= {}
|
403
|
+
heading_opts = {
|
404
|
+
:justification => column_opts[:justification]
|
405
|
+
}.merge(heading_opts)
|
406
|
+
end
|
407
|
+
specific = get_specific_column_options(table_data.column_names,
|
408
|
+
column_opts)
|
409
|
+
columns = table_data.column_names.inject({}) { |s,c|
|
410
|
+
s.merge( c => ::PDF::SimpleTable::Column.new(c) { |col|
|
411
|
+
col.heading = create_heading(heading_opts)
|
412
|
+
column_opts.each { |k,v| col.send("#{k}=",v) }
|
413
|
+
# use the specific column names now
|
414
|
+
specific[c].each { |k,v| col.send("#{k}=",v) }
|
415
|
+
})
|
416
|
+
}
|
417
|
+
table.columns = columns
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def get_specific_column_options(column_names,column_opts)
|
422
|
+
column_names.inject({}) do |s,c|
|
423
|
+
opts = column_opts.delete(c) || {}
|
424
|
+
if opts[:heading]
|
425
|
+
opts = opts.merge(:heading => create_heading(opts[:heading]))
|
426
|
+
end
|
427
|
+
s.merge(c => opts)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def create_heading(heading_opts)
|
432
|
+
heading_opts ||= {}
|
433
|
+
::PDF::SimpleTable::Column::Heading.new {|head|
|
434
|
+
heading_opts.each {|k,v| head.send("#{k}=",v) }
|
435
|
+
}
|
436
|
+
end
|
437
|
+
|
438
|
+
def grouping_columns
|
439
|
+
data.data.to_a[0][1].column_names.dup.unshift(data.grouped_by)
|
440
|
+
end
|
441
|
+
|
442
|
+
def table_with_grouped_by_column
|
443
|
+
Ruport::Data::Table.new(:column_names => grouping_columns)
|
444
|
+
end
|
445
|
+
|
446
|
+
def render_justified_or_separated_grouping
|
447
|
+
table = table_with_grouped_by_column
|
448
|
+
data.each do |name,group|
|
449
|
+
group.each_with_index do |r,i|
|
450
|
+
if i == 0
|
451
|
+
table << { data.grouped_by => "<b>#{name}</b>" }.merge(r.to_hash)
|
452
|
+
else
|
453
|
+
table << r
|
454
|
+
end
|
455
|
+
end
|
456
|
+
table << [" "] if options.style == :separated
|
457
|
+
end
|
458
|
+
render_table table, options.to_hash.merge(:formatter => pdf_writer)
|
459
|
+
end
|
460
|
+
|
461
|
+
def render_offset_grouping
|
462
|
+
table = table_with_grouped_by_column
|
463
|
+
data.each do |name,group|
|
464
|
+
table << ["<b>#{name}</b>"]
|
465
|
+
group.each {|r| table << r }
|
466
|
+
end
|
467
|
+
render_table table, options.to_hash.merge(:formatter => pdf_writer)
|
468
|
+
end
|
469
|
+
|
470
|
+
def image_fits_in_box?(img_width,box_width,img_height,box_height)
|
471
|
+
!(img_width > box_width || img_height > box_height)
|
472
|
+
end
|
473
|
+
|
474
|
+
def fit_image_in_box(img_width,box_width,img_height,box_height)
|
475
|
+
img_ratio = img_height.to_f / img_width.to_f
|
476
|
+
until image_fits_in_box?(img_width,box_width,img_height,box_height)
|
477
|
+
img_width -= 1
|
478
|
+
img_height = img_width * img_ratio
|
479
|
+
end
|
480
|
+
return img_width, img_height
|
481
|
+
end
|
482
|
+
|
483
|
+
def add_white_space(x,y,img_width,box_width,img_height,box_height)
|
484
|
+
if img_width < box_width
|
485
|
+
white_space = box_width - img_width
|
486
|
+
x = x + (white_space / 2)
|
487
|
+
end
|
488
|
+
if img_height < box_height
|
489
|
+
white_space = box_height - img_height
|
490
|
+
y = y + (white_space / 2)
|
491
|
+
end
|
492
|
+
return x, y
|
493
|
+
end
|
494
|
+
|
495
|
+
def resize_text_to_box(text,opts)
|
496
|
+
loop do
|
497
|
+
sz = pdf_writer.text_width(text, opts.font_size)
|
498
|
+
opts.x + sz > opts.x + opts.width or break
|
499
|
+
opts.font_size -= 1
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def draw_box(x,y,width,height,radius,fill_color=nil,stroke_color=nil)
|
504
|
+
pdf_writer.fill_color(fill_color || Color::RGB::White)
|
505
|
+
pdf_writer.stroke_color(stroke_color || Color::RGB::Black)
|
506
|
+
pdf_writer.rounded_rectangle(x, y, width, height, radius).fill_stroke
|
507
|
+
end
|
508
|
+
|
509
|
+
def add_text_with_bottom_border(text,x,y,width,font_size)
|
510
|
+
pdf_writer.line( x, y - 20,
|
511
|
+
x + width, y - 20).stroke
|
512
|
+
pdf_writer.fill_color(Color::RGB::Black)
|
513
|
+
move_cursor_to(y - 3)
|
514
|
+
add_text("<b>#{text}</b>",
|
515
|
+
:absolute_left => x, :absolute_right => x + width,
|
516
|
+
:justification => :center, :font_size => font_size)
|
517
|
+
end
|
518
|
+
|
519
|
+
def apply_page_format_template(t)
|
520
|
+
t = (t || {}).merge(options.page_format || {})
|
521
|
+
options.paper_size ||= t[:size]
|
522
|
+
options.paper_orientation ||= t[:layout]
|
523
|
+
end
|
524
|
+
|
525
|
+
def apply_text_format_template(t)
|
526
|
+
t = (t || {}).merge(options.text_format || {})
|
527
|
+
options.text_format = t unless t.empty?
|
528
|
+
end
|
529
|
+
|
530
|
+
def apply_table_format_template(t)
|
531
|
+
t = (t || {}).merge(options.table_format || {})
|
532
|
+
options.table_format = t unless t.empty?
|
533
|
+
end
|
534
|
+
|
535
|
+
def apply_column_format_template(t)
|
536
|
+
t = (t || {}).merge(options.column_format || {})
|
537
|
+
column_opts = {}
|
538
|
+
column_opts.merge!(:justification => t[:alignment]) if t[:alignment]
|
539
|
+
column_opts.merge!(:width => t[:width]) if t[:width]
|
540
|
+
unless column_opts.empty?
|
541
|
+
if options.table_format
|
542
|
+
if options.table_format[:column_options]
|
543
|
+
options.table_format[:column_options] =
|
544
|
+
column_opts.merge(options.table_format[:column_options])
|
545
|
+
else
|
546
|
+
options.table_format.merge!(:column_options => column_opts)
|
547
|
+
end
|
548
|
+
else
|
549
|
+
options.table_format = { :column_options => column_opts }
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
def apply_heading_format_template(t)
|
555
|
+
t = (t || {}).merge(options.heading_format || {})
|
556
|
+
heading_opts = {}
|
557
|
+
heading_opts.merge!(:justification => t[:alignment]) if t[:alignment]
|
558
|
+
heading_opts.merge!(:bold => t[:bold]) unless t[:bold].nil?
|
559
|
+
heading_opts.merge!(:title => t[:title]) if t[:title]
|
560
|
+
unless heading_opts.empty?
|
561
|
+
if options.table_format
|
562
|
+
if options.table_format[:column_options]
|
563
|
+
if options.table_format[:column_options][:heading]
|
564
|
+
options.table_format[:column_options][:heading] =
|
565
|
+
heading_opts.merge(
|
566
|
+
options.table_format[:column_options][:heading]
|
567
|
+
)
|
568
|
+
else
|
569
|
+
options.table_format[:column_options].merge!(
|
570
|
+
:heading => heading_opts
|
571
|
+
)
|
572
|
+
end
|
573
|
+
else
|
574
|
+
options.table_format.merge!(
|
575
|
+
:column_options => { :heading => heading_opts }
|
576
|
+
)
|
577
|
+
end
|
578
|
+
else
|
579
|
+
options.table_format = {
|
580
|
+
:column_options => { :heading => heading_opts }
|
581
|
+
}
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def apply_grouping_format_template(t)
|
587
|
+
t = (t || {}).merge(options.grouping_format || {})
|
588
|
+
options.style ||= t[:style]
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|