ruport 0.8.14 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +42 -107
- data/Rakefile +29 -32
- data/examples/centered_pdf_text_box.rb +13 -19
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +15 -15
- data/examples/pdf_complex_report.rb +10 -23
- data/examples/pdf_table_with_title.rb +12 -12
- data/examples/rope_examples/itunes/Rakefile +22 -1
- data/examples/rope_examples/itunes/config/environment.rb +4 -0
- data/examples/rope_examples/itunes/lib/init.rb +32 -2
- data/examples/rope_examples/itunes/util/build +50 -16
- data/examples/rope_examples/sales_report/README +1 -1
- data/examples/rope_examples/sales_report/Rakefile +22 -1
- data/examples/rope_examples/sales_report/config/environment.rb +4 -0
- data/examples/rope_examples/sales_report/lib/init.rb +32 -2
- data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
- data/examples/rope_examples/sales_report/util/build +50 -16
- data/examples/row_renderer.rb +39 -0
- data/examples/ruport_list/png_embed.rb +61 -0
- data/examples/ruport_list/roadmap.png +0 -0
- data/examples/sample.rb +16 -0
- data/examples/simple_pdf_lines.rb +24 -0
- data/lib/ruport.rb +143 -57
- data/lib/ruport/acts_as_reportable.rb +246 -0
- data/lib/ruport/data.rb +1 -2
- data/lib/ruport/data/grouping.rb +311 -0
- data/lib/ruport/data/record.rb +113 -84
- data/lib/ruport/data/table.rb +275 -174
- data/lib/ruport/formatter.rb +149 -0
- data/lib/ruport/formatter/csv.rb +87 -0
- data/lib/ruport/formatter/html.rb +89 -0
- data/lib/ruport/formatter/pdf.rb +357 -0
- data/lib/ruport/formatter/text.rb +151 -0
- data/lib/ruport/generator.rb +127 -30
- data/lib/ruport/query.rb +46 -99
- data/lib/ruport/renderer.rb +238 -194
- data/lib/ruport/renderer/grouping.rb +67 -0
- data/lib/ruport/renderer/table.rb +25 -98
- data/lib/ruport/report.rb +45 -96
- data/test/acts_as_reportable_test.rb +229 -0
- data/test/csv_formatter_test.rb +97 -0
- data/test/{_test_database.rb → database_test_.rb} +0 -0
- data/test/grouping_test.rb +305 -0
- data/test/html_formatter_test.rb +104 -0
- data/test/pdf_formatter_test.rb +25 -0
- data/test/{test_query.rb → query_test.rb} +32 -121
- data/test/{test_record.rb → record_test.rb} +40 -23
- data/test/renderer_test.rb +344 -0
- data/test/{test_report.rb → report_test.rb} +74 -44
- data/test/samples/ticket_count.csv +124 -0
- data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
- data/test/{test_table.rb → table_test.rb} +255 -44
- data/test/text_formatter_test.rb +144 -0
- data/util/bench/data/record/bench_as_vs_to.rb +17 -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 +121 -143
- data/TODO +0 -21
- data/examples/invoice.rb +0 -142
- data/examples/invoice_report.rb +0 -29
- data/examples/line_graph.rb +0 -38
- data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
- data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
- data/lib/ruport/attempt.rb +0 -63
- data/lib/ruport/config.rb +0 -204
- data/lib/ruport/data/groupable.rb +0 -93
- data/lib/ruport/data/taggable.rb +0 -80
- data/lib/ruport/format.rb +0 -1
- data/lib/ruport/format/csv.rb +0 -29
- data/lib/ruport/format/html.rb +0 -42
- data/lib/ruport/format/latex.rb +0 -47
- data/lib/ruport/format/pdf.rb +0 -233
- data/lib/ruport/format/plugin.rb +0 -31
- data/lib/ruport/format/svg.rb +0 -60
- data/lib/ruport/format/text.rb +0 -103
- data/lib/ruport/format/xml.rb +0 -32
- data/lib/ruport/layout.rb +0 -1
- data/lib/ruport/layout/component.rb +0 -7
- data/lib/ruport/mailer.rb +0 -99
- data/lib/ruport/renderer/graph.rb +0 -46
- data/lib/ruport/report/graph.rb +0 -14
- data/lib/ruport/system_extensions.rb +0 -71
- data/test/test_config.rb +0 -88
- data/test/test_format_text.rb +0 -63
- data/test/test_graph_renderer.rb +0 -97
- data/test/test_groupable.rb +0 -56
- data/test/test_mailer.rb +0 -170
- data/test/test_renderer.rb +0 -151
- data/test/test_ruport.rb +0 -58
- data/test/test_table_renderer.rb +0 -141
- data/test/test_taggable.rb +0 -52
@@ -0,0 +1,149 @@
|
|
1
|
+
# formatter.rb : Generalized formatting base class for Ruby Reports
|
2
|
+
#
|
3
|
+
# Created by Gregory Brown. Copyright December 2006, All Rights Reserved.
|
4
|
+
#
|
5
|
+
# This is free software, please see LICENSE and COPYING for details.
|
6
|
+
|
7
|
+
module Ruport
|
8
|
+
class Formatter
|
9
|
+
|
10
|
+
module RenderingTools
|
11
|
+
|
12
|
+
# Iterates through <tt>data</tt> and passes
|
13
|
+
# each row to render_row with the given options
|
14
|
+
def render_data_by_row(options={},&block)
|
15
|
+
data.each do |r|
|
16
|
+
render_row(r,options,&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Uses Renderer::Row to render the Row object with the
|
21
|
+
# given options.
|
22
|
+
#
|
23
|
+
# Sets the <tt>:io</tt> attribute by default to the existing
|
24
|
+
# formatter's <tt>output</tt> object.
|
25
|
+
def render_row(row,options={},&block)
|
26
|
+
render_helper(Renderer::Row,row,options,&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Uses Renderer::Table to render the Table object with the
|
30
|
+
# given options.
|
31
|
+
#
|
32
|
+
# Sets the :io attribute by default to the existing formatter's
|
33
|
+
# output object.
|
34
|
+
def render_table(table,options={},&block)
|
35
|
+
render_helper(Renderer::Table,table,options,&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Uses Renderer::Group to render the Group object with the
|
39
|
+
# given options.
|
40
|
+
#
|
41
|
+
# Sets the :io attribute by default to the existing formatter's
|
42
|
+
# output object.
|
43
|
+
def render_group(group,options={},&block)
|
44
|
+
render_helper(Renderer::Group,group,options,&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Uses Renderer::Grouping to render the Grouping object with the
|
48
|
+
# given options.
|
49
|
+
#
|
50
|
+
# Sets the :io attribute by default to the existing formatter's
|
51
|
+
# output object.
|
52
|
+
def render_grouping(grouping,options={},&block)
|
53
|
+
render_helper(Renderer::Grouping,grouping,options,&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Iterates through the data in the grouping and renders each group
|
57
|
+
# followed by a newline.
|
58
|
+
#
|
59
|
+
def render_inline_grouping(options={},&block)
|
60
|
+
data.each do |_,group|
|
61
|
+
render_group(group, options, &block)
|
62
|
+
output << "\n"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def render_helper(rend_klass, source_data,options={},&block)
|
69
|
+
options = {:data => source_data, :io => output}.merge(options)
|
70
|
+
rend_klass.render(format,options) do |rend|
|
71
|
+
block[rend] if block
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
include RenderingTools
|
78
|
+
|
79
|
+
attr_accessor :data
|
80
|
+
attr_accessor :format
|
81
|
+
attr_writer :options
|
82
|
+
|
83
|
+
# Registers the formatter with one or more Renderers
|
84
|
+
#
|
85
|
+
# renders :pdf, :for => MyRenderer
|
86
|
+
# render :text, :for => [MyRenderer,YourRenderer]
|
87
|
+
# renders [:csv,:html], :for => YourRenderer
|
88
|
+
#
|
89
|
+
def self.renders(fmts,options={})
|
90
|
+
Array(fmts).each do |format|
|
91
|
+
Array(options[:for]).each do |o|
|
92
|
+
o.send(:add_format,self,format)
|
93
|
+
formats << format unless formats.include?(format)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Allows the options specified to be accessed directly.
|
99
|
+
#
|
100
|
+
# opt_reader :something
|
101
|
+
# something == options.something #=> true
|
102
|
+
def self.opt_reader(*opts)
|
103
|
+
require "forwardable"
|
104
|
+
extend Forwardable
|
105
|
+
opts.each { |o| def_delegator :@options, o }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Gives a list of formats registered for this formatter.
|
109
|
+
def self.formats
|
110
|
+
@formats ||= []
|
111
|
+
end
|
112
|
+
|
113
|
+
# Stores a string used for outputting formatted data.
|
114
|
+
def output
|
115
|
+
return options.io if options.io
|
116
|
+
@output ||= ""
|
117
|
+
end
|
118
|
+
|
119
|
+
# Provides a generic OpenStruct for storing formatter options
|
120
|
+
def options
|
121
|
+
@options ||= Renderer::Options.new
|
122
|
+
end
|
123
|
+
|
124
|
+
# Clears output.
|
125
|
+
def clear_output
|
126
|
+
@output.replace("")
|
127
|
+
end
|
128
|
+
|
129
|
+
# Provides a shortcut for per format handlers.
|
130
|
+
#
|
131
|
+
# Example:
|
132
|
+
#
|
133
|
+
# # will only be called if formatter is called for html output
|
134
|
+
# html { output << "Look, I'm handling html" }
|
135
|
+
#
|
136
|
+
def method_missing(id,*args)
|
137
|
+
if self.class.formats.include?(id)
|
138
|
+
yield() if format == id
|
139
|
+
else
|
140
|
+
super
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
require "ruport/formatter/csv"
|
147
|
+
require "ruport/formatter/html"
|
148
|
+
require "ruport/formatter/text"
|
149
|
+
require "ruport/formatter/pdf"
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Ruport
|
2
|
+
|
3
|
+
# This formatter implements the CSV format for tabular data output.
|
4
|
+
class Formatter::CSV < Formatter
|
5
|
+
|
6
|
+
renders :csv, :for => [ Renderer::Row, Renderer::Table,
|
7
|
+
Renderer::Group, Renderer::Grouping ]
|
8
|
+
|
9
|
+
opt_reader :show_table_headers,
|
10
|
+
:format_options,
|
11
|
+
:show_group_headers,
|
12
|
+
:style
|
13
|
+
|
14
|
+
# Generates table header by turning column_names into a CSV row.
|
15
|
+
# Uses the row renderer to generate the actual formatted output
|
16
|
+
#
|
17
|
+
# This method does not do anything if options.show_table_headers is false
|
18
|
+
# or the Data::Table has no column names.
|
19
|
+
def build_table_header
|
20
|
+
unless data.column_names.empty? || !show_table_headers
|
21
|
+
render_row data.column_names, :format_options => format_options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Calls the row renderer for each row in the Data::Table
|
26
|
+
def build_table_body
|
27
|
+
render_data_by_row { |r|
|
28
|
+
r.options.format_options = format_options
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Produces CSV output for a data row.
|
33
|
+
def build_row
|
34
|
+
require "fastercsv"
|
35
|
+
output << FCSV.generate_line(data,format_options || {})
|
36
|
+
end
|
37
|
+
|
38
|
+
# Renders the header for a group using the group name.
|
39
|
+
#
|
40
|
+
def build_group_header
|
41
|
+
output << data.name.to_s << "\n\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Renders the group body - uses the table renderer to generate the output.
|
45
|
+
#
|
46
|
+
def build_group_body
|
47
|
+
render_table data, options.to_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generates a header for the grouping using the grouped_by column and the
|
51
|
+
# column names.
|
52
|
+
#
|
53
|
+
def build_grouping_header
|
54
|
+
unless style == :inline
|
55
|
+
output << "#{data.grouped_by}," << grouping_columns
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_grouping_body
|
60
|
+
case style
|
61
|
+
when :inline
|
62
|
+
render_inline_grouping(options)
|
63
|
+
when :justified, :raw
|
64
|
+
render_justified_or_raw_grouping
|
65
|
+
else
|
66
|
+
raise NotImplementedError, "Unknown style"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def grouping_columns
|
73
|
+
require "fastercsv"
|
74
|
+
data.data.to_a[0][1].column_names.to_csv
|
75
|
+
end
|
76
|
+
|
77
|
+
def render_justified_or_raw_grouping
|
78
|
+
data.each do |_,group|
|
79
|
+
output << "#{group.name}" if style == :justified
|
80
|
+
group.each do |row|
|
81
|
+
output << "#{group.name if style == :raw}," << row.to_csv
|
82
|
+
end
|
83
|
+
output << "\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Ruport
|
2
|
+
# Produces HTML output for tabular data.
|
3
|
+
#
|
4
|
+
class Formatter::HTML < Formatter
|
5
|
+
|
6
|
+
renders :html, :for => [ Renderer::Row, Renderer::Table,
|
7
|
+
Renderer::Group, Renderer::Grouping ]
|
8
|
+
|
9
|
+
opt_reader :show_table_headers, :show_group_headers
|
10
|
+
|
11
|
+
# Generates table headers based on the column names of your Data::Table.
|
12
|
+
#
|
13
|
+
# This method does not do anything if options.show_table_headers is false or
|
14
|
+
# the Data::Table has no column names.
|
15
|
+
def build_table_header
|
16
|
+
output << "\t<table>\n"
|
17
|
+
unless data.column_names.empty? || !show_table_headers
|
18
|
+
output << "\t\t<tr>\n\t\t\t<th>" +
|
19
|
+
data.column_names.join("</th>\n\t\t\t<th>") +
|
20
|
+
"</th>\n\t\t</tr>\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_table_body
|
25
|
+
render_data_by_row do |rend|
|
26
|
+
r = rend.data
|
27
|
+
rend.data = r.map { |e| e.to_s.empty? ? " " : e }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Simply closes the table tag.
|
32
|
+
def build_table_footer
|
33
|
+
output << "\t</table>"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Renders individual rows for the table
|
37
|
+
def build_row
|
38
|
+
output <<
|
39
|
+
"\t\t<tr>\n\t\t\t<td>" +
|
40
|
+
data.to_a.join("</td>\n\t\t\t<td>") +
|
41
|
+
"</td>\n\t\t</tr>\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Renders the header for a group using the group name.
|
45
|
+
#
|
46
|
+
def build_group_header
|
47
|
+
output << "\t<p>#{data.name}</p>\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates the group body. Since group data is a table, just uses the
|
51
|
+
# Table renderer.
|
52
|
+
#
|
53
|
+
def build_group_body
|
54
|
+
render_table data, options.to_hash
|
55
|
+
end
|
56
|
+
|
57
|
+
# Generates the body for a grouping. Iterates through the groups and
|
58
|
+
# renders them using the group renderer.
|
59
|
+
#
|
60
|
+
def build_grouping_body
|
61
|
+
render_inline_grouping(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Generates <table> tags enclosing the yielded content.
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# output << html_table { "<tr><td>1</td><td>2</td></tr>\n" }
|
69
|
+
# #=> "<table>\n<tr><td>1</td><td>2</td></tr>\n</table>\n"
|
70
|
+
#
|
71
|
+
def html_table
|
72
|
+
"<table>\n" << yield << "</table>\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Uses RedCloth to turn a string containing textile markup into HTML.
|
76
|
+
#
|
77
|
+
# Example:
|
78
|
+
#
|
79
|
+
# textile "*bar*" #=> "<p><strong>foo</strong></p>"
|
80
|
+
#
|
81
|
+
def textile(s)
|
82
|
+
require "redcloth"
|
83
|
+
RedCloth.new(s).to_html
|
84
|
+
rescue LoadError
|
85
|
+
raise RuntimeError, "You need RedCloth!\n gem install RedCloth -v 3.0.3"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
module Ruport
|
2
|
+
|
3
|
+
# PDF generation formatter
|
4
|
+
#
|
5
|
+
# options:
|
6
|
+
# General:
|
7
|
+
# * paper_size #=> "LETTER"
|
8
|
+
# * paper_orientation #=> :portrait
|
9
|
+
#
|
10
|
+
# Text:
|
11
|
+
# * text_format
|
12
|
+
#
|
13
|
+
# Table:
|
14
|
+
# * table_format (a hash that can take any of the options available
|
15
|
+
# to PDF::SimpleTable)
|
16
|
+
# * table_format[:maximum_width] #=> 500
|
17
|
+
#
|
18
|
+
class Formatter::PDF < Formatter
|
19
|
+
|
20
|
+
renders :pdf, :for => [ Renderer::Row, Renderer::Table,
|
21
|
+
Renderer::Group, Renderer::Grouping ]
|
22
|
+
|
23
|
+
attr_writer :pdf_writer
|
24
|
+
attr_accessor :table_header_proc
|
25
|
+
attr_accessor :table_footer_proc
|
26
|
+
|
27
|
+
opt_reader :show_table_headers,
|
28
|
+
:style,
|
29
|
+
:table_format,
|
30
|
+
:text_format,
|
31
|
+
:paper_size,
|
32
|
+
:paper_orientation
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
quiet do
|
36
|
+
require "pdf/writer"
|
37
|
+
require "pdf/simpletable"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the current PDF::Writer object or creates a new one if it has not
|
42
|
+
# been set yet.
|
43
|
+
#
|
44
|
+
def pdf_writer
|
45
|
+
@pdf_writer ||= options.formatter ||
|
46
|
+
::PDF::Writer.new( :paper => paper_size || "LETTER",
|
47
|
+
:orientation => paper_orientation || :portrait)
|
48
|
+
@pdf_writer.extend(PDFWriterMemoryPatch)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Calls the draw_table method.
|
52
|
+
#
|
53
|
+
def build_table_body
|
54
|
+
draw_table(data)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Appends the results of PDF::Writer#render to output for your
|
58
|
+
# <tt>pdf_writer</tt> object.
|
59
|
+
#
|
60
|
+
def finalize_table
|
61
|
+
render_pdf unless options.skip_finalize_table
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_group_header
|
65
|
+
pad(10) { add_text data.name.to_s, :justification => :center }
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_group_body
|
69
|
+
render_table data, options.to_hash.merge(:formatter => pdf_writer)
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_grouping_body
|
73
|
+
case style
|
74
|
+
when :inline
|
75
|
+
render_inline_grouping(options.to_hash.merge(:formatter => pdf_writer,
|
76
|
+
:skip_finalize_table => true))
|
77
|
+
when :justified, :separated
|
78
|
+
render_justified_or_separated_grouping
|
79
|
+
when :offset
|
80
|
+
render_offset_grouping
|
81
|
+
else
|
82
|
+
raise NotImplementedError, "Unknown style"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def finalize_grouping
|
87
|
+
render_pdf
|
88
|
+
end
|
89
|
+
|
90
|
+
# Call PDF::Writer#text with the given arguments
|
91
|
+
def add_text(text, format_opts={})
|
92
|
+
format_opts = text_format.merge(format_opts) if text_format
|
93
|
+
pdf_writer.text(text, format_opts)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Calls PDF::Writer#render and appends to <tt>output</tt>
|
97
|
+
def render_pdf
|
98
|
+
output << pdf_writer.render
|
99
|
+
end
|
100
|
+
|
101
|
+
# - If the image is bigger than the box, it will be scaled down until
|
102
|
+
# it fits.
|
103
|
+
# - If the image is smaller than the box, it won't be resized.
|
104
|
+
#
|
105
|
+
# options:
|
106
|
+
# - :x: left bound of box
|
107
|
+
# - :y: bottom bound of box
|
108
|
+
# - :width: width of box
|
109
|
+
# - :height: height of box
|
110
|
+
#
|
111
|
+
def center_image_in_box(path, image_opts={})
|
112
|
+
x = image_opts[:x]
|
113
|
+
y = image_opts[:y]
|
114
|
+
width = image_opts[:width]
|
115
|
+
height = image_opts[:height]
|
116
|
+
info = ::PDF::Writer::Graphics::ImageInfo.new(File.read(path))
|
117
|
+
|
118
|
+
# reduce the size of the image until it fits into the requested box
|
119
|
+
img_width, img_height =
|
120
|
+
fit_image_in_box(info.width,width,info.height,height)
|
121
|
+
|
122
|
+
# if the image is smaller than the box, calculate the white space buffer
|
123
|
+
x, y = add_white_space(x,y,img_width,width,img_height,height)
|
124
|
+
|
125
|
+
pdf_writer.add_image_from_file(path, x, y, img_width, img_height)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Draws some text on the canvas, surrounded by a box with rounded corners
|
129
|
+
#
|
130
|
+
def rounded_text_box(text)
|
131
|
+
opts = OpenStruct.new
|
132
|
+
yield(opts)
|
133
|
+
|
134
|
+
resize_text_to_box(text, opts)
|
135
|
+
|
136
|
+
pdf_writer.save_state
|
137
|
+
draw_box(opts.x, opts.y, opts.width, opts.height, opts.radius,
|
138
|
+
opts.fill_color, opts.stroke_color)
|
139
|
+
add_text_with_bottom_border(opts.heading, opts.x, opts.y,
|
140
|
+
opts.width, opts.font_size) if opts.heading
|
141
|
+
pdf_writer.restore_state
|
142
|
+
|
143
|
+
start_position = opts.heading ? opts.y - 20 : opts.y
|
144
|
+
draw_text(text, :y => start_position,
|
145
|
+
:left => opts.x,
|
146
|
+
:right => opts.x + opts.width,
|
147
|
+
:justification => opts.justification || :center,
|
148
|
+
:font_size => opts.font_size)
|
149
|
+
move_cursor_to(opts.y - opts.height)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Adds an image to every page. The color and size won't be modified,
|
153
|
+
# but it will be centered.
|
154
|
+
#
|
155
|
+
def watermark(imgpath)
|
156
|
+
x = pdf_writer.absolute_left_margin
|
157
|
+
y = pdf_writer.absolute_bottom_margin
|
158
|
+
width = pdf_writer.absolute_right_margin - x
|
159
|
+
height = pdf_writer.absolute_top_margin - y
|
160
|
+
|
161
|
+
pdf_writer.open_object do |wm|
|
162
|
+
pdf_writer.save_state
|
163
|
+
center_image_in_box(imgpath, :x => x, :y => y,
|
164
|
+
:width => width, :height => height)
|
165
|
+
pdf_writer.restore_state
|
166
|
+
pdf_writer.close_object
|
167
|
+
pdf_writer.add_object(wm, :all_pages)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def move_cursor(n)
|
172
|
+
pdf_writer.y += n
|
173
|
+
end
|
174
|
+
|
175
|
+
def move_cursor_to(n)
|
176
|
+
pdf_writer.y = n
|
177
|
+
end
|
178
|
+
|
179
|
+
def pad(y,&block)
|
180
|
+
move_cursor(-y)
|
181
|
+
block.call
|
182
|
+
move_cursor(-y)
|
183
|
+
end
|
184
|
+
|
185
|
+
def pad_top(y,&block)
|
186
|
+
move_cursor(-y)
|
187
|
+
block.call
|
188
|
+
end
|
189
|
+
|
190
|
+
def pad_bottom(y,&block)
|
191
|
+
block.call
|
192
|
+
move_cursor(-y)
|
193
|
+
end
|
194
|
+
|
195
|
+
def draw_table(table_data, format_opts={})
|
196
|
+
m = "Sorry, cant build PDFs from array like things (yet)"
|
197
|
+
raise m if table_data.column_names.empty?
|
198
|
+
|
199
|
+
format_opts = table_format.merge(format_opts) if table_format
|
200
|
+
|
201
|
+
::PDF::SimpleTable.new do |table|
|
202
|
+
table.data = table_data
|
203
|
+
table.maximum_width = 500
|
204
|
+
table.column_order = table_data.column_names
|
205
|
+
|
206
|
+
apply_pdf_table_column_opts(table,table_data,format_opts)
|
207
|
+
|
208
|
+
format_opts.each {|k,v| table.send("#{k}=", v) }
|
209
|
+
|
210
|
+
table.render_on(pdf_writer)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
module DrawingHelpers
|
215
|
+
|
216
|
+
def horizontal_line(x1,x2)
|
217
|
+
pdf_writer.line(x1,cursor,x2,cursor)
|
218
|
+
pdf_writer.stroke
|
219
|
+
end
|
220
|
+
|
221
|
+
def vertical_line_at(x,y1,y2)
|
222
|
+
pdf_writer.line(x,y1,x,y2)
|
223
|
+
end
|
224
|
+
|
225
|
+
def left_boundary
|
226
|
+
pdf_writer.absolute_left_margin
|
227
|
+
end
|
228
|
+
|
229
|
+
def right_boundary
|
230
|
+
pdf_writer.absolute_right_margin
|
231
|
+
end
|
232
|
+
|
233
|
+
def top_boundary
|
234
|
+
pdf_writer.absolute_top_margin
|
235
|
+
end
|
236
|
+
|
237
|
+
def bottom_boundary
|
238
|
+
pdf_writer.absolute_bottom_margin
|
239
|
+
end
|
240
|
+
|
241
|
+
def cursor
|
242
|
+
pdf_writer.y
|
243
|
+
end
|
244
|
+
|
245
|
+
def draw_text(text,text_opts)
|
246
|
+
move_cursor_to(text_opts[:y]) if text_opts[:y]
|
247
|
+
add_text(text,
|
248
|
+
text_opts.merge(:absolute_left => text_opts[:x1] || text_opts[:left],
|
249
|
+
:absolute_right => text_opts[:x2] || text_opts[:right]))
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
include DrawingHelpers
|
255
|
+
|
256
|
+
module PDFWriterMemoryPatch #:nodoc:
|
257
|
+
unless self.class.instance_methods.include?("_post_transaction_rewind")
|
258
|
+
def _post_transaction_rewind
|
259
|
+
@objects.each { |e| e.instance_variable_set(:@parent,self) }
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def apply_pdf_table_column_opts(table,table_data,format_opts)
|
267
|
+
column_opts = format_opts.delete(:column_options)
|
268
|
+
if column_opts
|
269
|
+
columns = table_data.column_names.inject({}) { |s,c|
|
270
|
+
s.merge( c => ::PDF::SimpleTable::Column.new(c) { |col|
|
271
|
+
column_opts.each { |k,v| col.send("#{k}=",v) }
|
272
|
+
})
|
273
|
+
}
|
274
|
+
table.columns = columns
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def grouping_columns
|
279
|
+
data.data.to_a[0][1].column_names.dup.unshift(data.grouped_by)
|
280
|
+
end
|
281
|
+
|
282
|
+
def table_with_grouped_by_column
|
283
|
+
Ruport::Data::Table.new(:column_names => grouping_columns)
|
284
|
+
end
|
285
|
+
|
286
|
+
def render_justified_or_separated_grouping
|
287
|
+
table = table_with_grouped_by_column
|
288
|
+
data.each do |name,group|
|
289
|
+
group_column = { data.grouped_by => "<b>#{name}</b>\n" }
|
290
|
+
group.each_with_index do |rec,i|
|
291
|
+
i == 0 ? table << group_column.merge(rec.to_h) : table << rec
|
292
|
+
end
|
293
|
+
table << Array.new(grouping_columns.length,' ') if style == :separated
|
294
|
+
end
|
295
|
+
render_table table, options.to_hash.merge(:formatter => pdf_writer)
|
296
|
+
end
|
297
|
+
|
298
|
+
def render_offset_grouping
|
299
|
+
table = table_with_grouped_by_column
|
300
|
+
data.each do |name,group|
|
301
|
+
table << ["<b>#{name}</b>\n",nil,nil]
|
302
|
+
group.each {|r| table << r }
|
303
|
+
end
|
304
|
+
render_table table, options.to_hash.merge(:formatter => pdf_writer)
|
305
|
+
end
|
306
|
+
|
307
|
+
def image_fits_in_box?(img_width,box_width,img_height,box_height)
|
308
|
+
!(img_width > box_width || img_height > box_height)
|
309
|
+
end
|
310
|
+
|
311
|
+
def fit_image_in_box(img_width,box_width,img_height,box_height)
|
312
|
+
img_ratio = img_height.to_f / img_width.to_f
|
313
|
+
until image_fits_in_box?(img_width,box_width,img_height,box_height)
|
314
|
+
img_width -= 1
|
315
|
+
img_height = img_width * img_ratio
|
316
|
+
end
|
317
|
+
return img_width, img_height
|
318
|
+
end
|
319
|
+
|
320
|
+
def add_white_space(x,y,img_width,box_width,img_height,box_height)
|
321
|
+
if img_width < box_width
|
322
|
+
white_space = box_width - img_width
|
323
|
+
x = x + (white_space / 2)
|
324
|
+
end
|
325
|
+
if img_height < box_height
|
326
|
+
white_space = box_height - img_height
|
327
|
+
y = y + (white_space / 2)
|
328
|
+
end
|
329
|
+
return x, y
|
330
|
+
end
|
331
|
+
|
332
|
+
def resize_text_to_box(text,opts)
|
333
|
+
loop do
|
334
|
+
sz = pdf_writer.text_width(text, opts.font_size)
|
335
|
+
opts.x + sz > opts.x + opts.width or break
|
336
|
+
opts.font_size -= 1
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def draw_box(x,y,width,height,radius,fill_color=nil,stroke_color=nil)
|
341
|
+
pdf_writer.fill_color(fill_color || Color::RGB::White)
|
342
|
+
pdf_writer.stroke_color(stroke_color || Color::RGB::Black)
|
343
|
+
pdf_writer.rounded_rectangle(x, y, width, height, radius).fill_stroke
|
344
|
+
end
|
345
|
+
|
346
|
+
def add_text_with_bottom_border(text,x,y,width,font_size)
|
347
|
+
pdf_writer.line( x, y - 20,
|
348
|
+
x + width, y - 20).stroke
|
349
|
+
pdf_writer.fill_color(Color::RGB::Black)
|
350
|
+
move_cursor_to(y - 3)
|
351
|
+
add_text("<b>#{text}</b>",
|
352
|
+
:absolute_left => x, :absolute_right => x + width,
|
353
|
+
:justification => :center, :font_size => font_size)
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|