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.
Files changed (102) hide show
  1. data/README +42 -107
  2. data/Rakefile +29 -32
  3. data/examples/centered_pdf_text_box.rb +13 -19
  4. data/examples/example.csv +3 -0
  5. data/examples/line_plotter.rb +15 -15
  6. data/examples/pdf_complex_report.rb +10 -23
  7. data/examples/pdf_table_with_title.rb +12 -12
  8. data/examples/rope_examples/itunes/Rakefile +22 -1
  9. data/examples/rope_examples/itunes/config/environment.rb +4 -0
  10. data/examples/rope_examples/itunes/lib/init.rb +32 -2
  11. data/examples/rope_examples/itunes/util/build +50 -16
  12. data/examples/rope_examples/sales_report/README +1 -1
  13. data/examples/rope_examples/sales_report/Rakefile +22 -1
  14. data/examples/rope_examples/sales_report/config/environment.rb +4 -0
  15. data/examples/rope_examples/sales_report/lib/init.rb +32 -2
  16. data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
  17. data/examples/rope_examples/sales_report/util/build +50 -16
  18. data/examples/row_renderer.rb +39 -0
  19. data/examples/ruport_list/png_embed.rb +61 -0
  20. data/examples/ruport_list/roadmap.png +0 -0
  21. data/examples/sample.rb +16 -0
  22. data/examples/simple_pdf_lines.rb +24 -0
  23. data/lib/ruport.rb +143 -57
  24. data/lib/ruport/acts_as_reportable.rb +246 -0
  25. data/lib/ruport/data.rb +1 -2
  26. data/lib/ruport/data/grouping.rb +311 -0
  27. data/lib/ruport/data/record.rb +113 -84
  28. data/lib/ruport/data/table.rb +275 -174
  29. data/lib/ruport/formatter.rb +149 -0
  30. data/lib/ruport/formatter/csv.rb +87 -0
  31. data/lib/ruport/formatter/html.rb +89 -0
  32. data/lib/ruport/formatter/pdf.rb +357 -0
  33. data/lib/ruport/formatter/text.rb +151 -0
  34. data/lib/ruport/generator.rb +127 -30
  35. data/lib/ruport/query.rb +46 -99
  36. data/lib/ruport/renderer.rb +238 -194
  37. data/lib/ruport/renderer/grouping.rb +67 -0
  38. data/lib/ruport/renderer/table.rb +25 -98
  39. data/lib/ruport/report.rb +45 -96
  40. data/test/acts_as_reportable_test.rb +229 -0
  41. data/test/csv_formatter_test.rb +97 -0
  42. data/test/{_test_database.rb → database_test_.rb} +0 -0
  43. data/test/grouping_test.rb +305 -0
  44. data/test/html_formatter_test.rb +104 -0
  45. data/test/pdf_formatter_test.rb +25 -0
  46. data/test/{test_query.rb → query_test.rb} +32 -121
  47. data/test/{test_record.rb → record_test.rb} +40 -23
  48. data/test/renderer_test.rb +344 -0
  49. data/test/{test_report.rb → report_test.rb} +74 -44
  50. data/test/samples/ticket_count.csv +124 -0
  51. data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
  52. data/test/{test_table.rb → table_test.rb} +255 -44
  53. data/test/text_formatter_test.rb +144 -0
  54. data/util/bench/data/record/bench_as_vs_to.rb +17 -0
  55. data/util/bench/data/record/bench_constructor.rb +46 -0
  56. data/util/bench/data/record/bench_indexing.rb +65 -0
  57. data/util/bench/data/record/bench_reorder.rb +35 -0
  58. data/util/bench/data/record/bench_to_a.rb +19 -0
  59. data/util/bench/data/table/bench_column_manip.rb +103 -0
  60. data/util/bench/data/table/bench_dup.rb +24 -0
  61. data/util/bench/data/table/bench_init.rb +67 -0
  62. data/util/bench/data/table/bench_manip.rb +125 -0
  63. data/util/bench/formatter/bench_csv.rb +14 -0
  64. data/util/bench/formatter/bench_html.rb +14 -0
  65. data/util/bench/formatter/bench_pdf.rb +14 -0
  66. data/util/bench/formatter/bench_text.rb +14 -0
  67. data/util/bench/samples/tattle.csv +1237 -0
  68. metadata +121 -143
  69. data/TODO +0 -21
  70. data/examples/invoice.rb +0 -142
  71. data/examples/invoice_report.rb +0 -29
  72. data/examples/line_graph.rb +0 -38
  73. data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
  74. data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
  75. data/lib/ruport/attempt.rb +0 -63
  76. data/lib/ruport/config.rb +0 -204
  77. data/lib/ruport/data/groupable.rb +0 -93
  78. data/lib/ruport/data/taggable.rb +0 -80
  79. data/lib/ruport/format.rb +0 -1
  80. data/lib/ruport/format/csv.rb +0 -29
  81. data/lib/ruport/format/html.rb +0 -42
  82. data/lib/ruport/format/latex.rb +0 -47
  83. data/lib/ruport/format/pdf.rb +0 -233
  84. data/lib/ruport/format/plugin.rb +0 -31
  85. data/lib/ruport/format/svg.rb +0 -60
  86. data/lib/ruport/format/text.rb +0 -103
  87. data/lib/ruport/format/xml.rb +0 -32
  88. data/lib/ruport/layout.rb +0 -1
  89. data/lib/ruport/layout/component.rb +0 -7
  90. data/lib/ruport/mailer.rb +0 -99
  91. data/lib/ruport/renderer/graph.rb +0 -46
  92. data/lib/ruport/report/graph.rb +0 -14
  93. data/lib/ruport/system_extensions.rb +0 -71
  94. data/test/test_config.rb +0 -88
  95. data/test/test_format_text.rb +0 -63
  96. data/test/test_graph_renderer.rb +0 -97
  97. data/test/test_groupable.rb +0 -56
  98. data/test/test_mailer.rb +0 -170
  99. data/test/test_renderer.rb +0 -151
  100. data/test/test_ruport.rb +0 -58
  101. data/test/test_table_renderer.rb +0 -141
  102. 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? ? "&nbsp;" : 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