ruport 0.8.14 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|