tn_pdf 0.0.2 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 91cb0ad993ddca5fbf6fe2c810cd947e59c5cb3f
4
+ data.tar.gz: 2aa092c489969b92567973b078b52dd90aac4c4b
5
+ SHA512:
6
+ metadata.gz: c7e55005eecbf3e0dce9b5997efd57e33a62bb1075921ffddb4639e133175ef00ba3ecbbf9ed554eb5ba7c7ee5cc7ed3d7d59086e82d5c95f57c3c8d5bdf787e
7
+ data.tar.gz: 92dd019191ee1f274d5ea381ce53feb5ce45ded1816643e6c2f570bbabdde97bc239e55079f97f799a5b8fb9847dd133a766abb8712d24256e10886aed33461d
data/.gitignore CHANGED
@@ -7,3 +7,6 @@ coverage
7
7
  *.gem
8
8
  Gemfile.lock
9
9
  pkg/*
10
+ doc/
11
+ .yardoc
12
+ *.rbc
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.8.7 || rvm use ree || rvm use default
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in tn_pdf.gemspec
4
4
  gemspec
5
+
6
+ gem 'rake'
7
+ gem 'pry'
data/Gemfile.lock CHANGED
@@ -1,47 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tn_pdf (0.0.8)
5
+ backports
6
+ prawn (~> 0.11.1)
7
+
1
8
  GEM
2
9
  remote: http://rubygems.org/
3
10
  specs:
4
- Ascii85 (1.0.1)
5
- ZenTest (4.5.0)
6
- activesupport (2.3.5)
7
- autotest (4.4.6)
8
- ZenTest (>= 4.4.1)
11
+ Ascii85 (1.0.2)
12
+ afm (0.2.2)
13
+ backports (3.6.0)
9
14
  coderay (0.9.8)
10
15
  diff-lcs (1.1.2)
11
- method_source (0.4.1)
16
+ hashery (2.1.1)
17
+ method_source (0.6.0)
12
18
  ruby_parser (>= 2.0.5)
13
- pdf-reader (0.9.2)
14
- Ascii85 (>= 0.9)
19
+ pdf-reader (1.3.3)
20
+ Ascii85 (~> 1.0.0)
21
+ afm (~> 0.2.0)
22
+ hashery (~> 2.0)
23
+ ruby-rc4
24
+ ttfunk
15
25
  prawn (0.11.1)
16
26
  pdf-reader (>= 0.9.0)
17
27
  ttfunk (~> 1.0.0)
18
- pry (0.8.3)
19
- coderay (>= 0.9.7)
20
- method_source (>= 0.4.0)
28
+ pry (0.9.2)
29
+ coderay (>= 0.9.8)
30
+ method_source (>= 0.6.0)
21
31
  ruby_parser (>= 2.0.5)
22
- slop (>= 1.5.3)
23
- rcov (0.9.9)
32
+ slop (~> 1.9.0)
33
+ rake (0.9.2)
24
34
  rspec (2.6.0)
25
35
  rspec-core (~> 2.6.0)
26
36
  rspec-expectations (~> 2.6.0)
27
37
  rspec-mocks (~> 2.6.0)
28
- rspec-core (2.6.1)
38
+ rspec-core (2.6.4)
29
39
  rspec-expectations (2.6.0)
30
40
  diff-lcs (~> 1.1.2)
31
41
  rspec-mocks (2.6.0)
42
+ ruby-rc4 (0.1.5)
32
43
  ruby_parser (2.0.6)
33
44
  sexp_processor (~> 3.0)
34
45
  sexp_processor (3.0.5)
35
- slop (1.6.0)
36
- ttfunk (1.0.1)
46
+ slop (1.9.1)
47
+ ttfunk (1.0.3)
37
48
 
38
49
  PLATFORMS
39
50
  ruby
40
51
 
41
52
  DEPENDENCIES
42
- activesupport
43
- autotest
44
- prawn (~> 0.11.1)
45
53
  pry
46
- rcov
54
+ rake
47
55
  rspec (~> 2.6.0)
56
+ tn_pdf!
data/Rakefile CHANGED
@@ -2,11 +2,10 @@ require 'rake'
2
2
  require 'bundler'
3
3
  Bundler::GemHelper.install_tasks
4
4
 
5
- require 'spec/rake/spectask'
5
+ require 'rspec/core/rake_task'
6
6
 
7
7
  desc "Run all examples with RCov"
8
- Spec::Rake::SpecTask.new('examples_with_rcov') do |t|
9
- t.spec_files = FileList['spec/**/*_spec.rb']
8
+ RSpec::Core::RakeTask.new('examples_with_rcov') do |t|
10
9
  t.rcov = true
11
- t.rcov_opts = ['--exclude', 'spec,.bundle']
10
+ # t.rcov_opts = ['--exclude', 'spec,.bundle']
12
11
  end
@@ -0,0 +1,11 @@
1
+ module Prawn
2
+ class Table
3
+ alias_method :column_widths_broken=, :column_widths=
4
+
5
+ def column_widths_fixed=(value)
6
+ @column_widths = (column_widths_broken = value)
7
+ end
8
+
9
+ alias_method :column_widths=, :column_widths_fixed=
10
+ end
11
+ end
data/lib/tn_pdf/box.rb CHANGED
@@ -2,34 +2,38 @@ module TnPDF
2
2
  class PageSection
3
3
 
4
4
  class Box
5
- attr_reader :image_path, :image_options
5
+ attr_reader :image_file, :image_options
6
6
  attr_reader :text, :text_options
7
+ attr_accessor :width
7
8
 
8
9
  def initialize(options)
9
10
  parse_options(options)
10
11
  end
11
12
 
12
- def render(document, pos, width, height=nil)
13
+ def render(document, pos, height=nil)
13
14
  options_hash = { :width => width }
14
15
  options_hash[:height] = height unless height.nil?
15
16
 
16
17
  document.bounding_box(pos, options_hash) do
17
18
  if has_image?
18
- image_args = [image_path]
19
+ image_args = [image_file]
19
20
  image_args << image_options unless image_options.empty?
20
21
  document.image *image_args
21
22
  end
22
23
 
23
24
  if has_text?
24
25
  text_args = [text]
25
- text_args << text_options unless text_options.empty?
26
- document.text *text_args
26
+ text_options.merge!({:inline_format => true})
27
+ text_args << text_options
28
+ document.font(text_options[:font], text_options[:font_options]) do
29
+ document.text *text_args
30
+ end
27
31
  end
28
32
  end
29
33
  end
30
34
 
31
35
  def has_image?
32
- !image_path.nil?
36
+ !image_file.nil?
33
37
  end
34
38
 
35
39
  def has_text?
@@ -39,8 +43,9 @@ module TnPDF
39
43
  private
40
44
 
41
45
  def parse_options(options)
42
- options.to_options!
46
+ options = Configuration.perform_conversions(options)
43
47
 
48
+ self.width = options[:width]
44
49
  options[:align] ||= :justify
45
50
  options[:valign] ||= :center
46
51
 
@@ -51,19 +56,31 @@ module TnPDF
51
56
  options[:text][:align] = options[:align]
52
57
  options[:text][:valign] = options[:valign]
53
58
 
59
+ options[:text][:font] ||= Configuration[:font]
60
+ options[:text][:font_options] = {}
61
+ options[:text][:font_options][:size] =
62
+ options[:text][:font_size] if options[:text][:font_size]
63
+
64
+ options[:text][:font_options][:style] =
65
+ options[:text][:font_style] if options[:text][:font_style]
66
+
54
67
  @text = options[:text][:text]
55
68
  @text_options = options[:text].reject { |k,_| k == :text }
56
69
  end
57
70
 
58
71
  if options[:image]
59
72
  unless options[:image].kind_of? Hash
60
- options[:image] = { :path => options[:image] }
73
+ options[:image] = { :file => options[:image] }
61
74
  end
62
75
  options[:image][:position] = options[:align]
63
76
  options[:image][:vposition] = options[:valign]
64
77
 
65
- @image_path = options[:image][:path]
66
- @image_options = options[:image].reject { |k,_| k == :path }
78
+ path = Configuration[:images_path]
79
+ @image_file = options[:image][:file]
80
+ unless @image_file =~ /^#{path}/
81
+ @image_file = File.join(path, @image_file)
82
+ end
83
+ @image_options = options[:image].reject { |k,_| k == :file }
67
84
  end
68
85
  end
69
86
 
@@ -4,27 +4,24 @@ module TnPDF
4
4
  class Configuration
5
5
  class << self
6
6
 
7
-
8
7
  def [](property)
9
- property = property.to_s
10
- case property
11
- when /^page_header_/
12
- property_key = property.sub('page_header_','').to_sym
13
- header[property_key]
14
- when /^page_footer_/
15
- property_key = property.sub('page_footer_','').to_sym
16
- footer[property_key]
17
- when /^table_/
18
- property_key = property.sub('table_','').to_sym
19
- table[property_key]
20
- when /^column_/
21
- property_key = property.sub('column_','').to_sym
22
- column[property_key]
23
- else
24
- report[property.to_sym]
8
+ (hash, key) = filter_property(property)
9
+ value = hash[key]
10
+
11
+ if value.kind_of?(Proc)
12
+ value.call
13
+ else
14
+ value
25
15
  end
26
16
  end
27
17
 
18
+ def []=(property, value)
19
+ (hash, key) = filter_property(property)
20
+ value = perform_conversions(value)
21
+
22
+ hash[key] = value
23
+ end
24
+
28
25
  def report_properties_names
29
26
  report.keys
30
27
  end
@@ -50,9 +47,31 @@ module TnPDF
50
47
 
51
48
  def load_from(yaml_file)
52
49
  configurations = YAML.load_file(yaml_file)
53
- configurations.to_options!
54
- configurations.each_key do |item|
55
- self.send(item).merge! configurations[item].to_options!
50
+
51
+ configurations.each do |item, value|
52
+ value = perform_conversions(value)
53
+ self.send(item).merge! value
54
+ end
55
+ end
56
+
57
+ def perform_conversions(value)
58
+ match = value.match(/^(\d+\.?\d*)(cm|mm)$/) rescue nil
59
+ if match
60
+ num = match[1].to_f
61
+ conversion = match[2].to_sym
62
+ num.send(conversion)
63
+ elsif value.kind_of? Hash
64
+ value.inject({}) do |hash, (key, value)|
65
+ hash[key.to_sym] = perform_conversions(value)
66
+ hash
67
+ end
68
+ elsif value.kind_of? Array
69
+ value.inject([]) do |array, value|
70
+ array << perform_conversions(value)
71
+ array
72
+ end
73
+ else
74
+ value
56
75
  end
57
76
  end
58
77
 
@@ -68,6 +87,9 @@ module TnPDF
68
87
  :bottom_margin => 0.cm,
69
88
  :font => "Courier",
70
89
  :font_size => 10,
90
+ :images_path => "./",
91
+ :text_before_table => "",
92
+ :text_after_table => "",
71
93
  }
72
94
  end
73
95
 
@@ -76,6 +98,9 @@ module TnPDF
76
98
  :height => 1.cm,
77
99
  :top => 0.5.cm,
78
100
  :bottom => 0.2.cm,
101
+ :left => {},
102
+ :right => {},
103
+ :center => {}
79
104
  }
80
105
  end
81
106
 
@@ -85,17 +110,30 @@ module TnPDF
85
110
  :height => 1.cm,
86
111
  :top => 0.2.cm,
87
112
  :bottom => 0.1.cm,
113
+ :left => {},
114
+ :right => {},
115
+ :center => {}
88
116
  }
89
117
  end
90
118
 
91
119
  def table
92
120
  @table ||= {
93
121
  :align => :center,
122
+ :text_before => "",
123
+ :text_after => "",
94
124
  :multipage_headers => true,
95
- :borders => false,
125
+ :font_size => 10,
126
+ :header_font_style => :bold,
127
+ :header_font_size => 10,
128
+ :header_font => 'Courier',
96
129
  :header_color => "FF0000",
130
+ :footer_font_style => :bold,
131
+ :footer_font_size => 10,
132
+ :footer_font => 'Courier',
133
+ :footer_color => "FF0000",
97
134
  :odd_row_color => "00FF00",
98
135
  :even_row_color => "0000FF",
136
+ :borders => false,
99
137
  }
100
138
  end
101
139
 
@@ -116,9 +154,33 @@ module TnPDF
116
154
  :decimal => "," },
117
155
 
118
156
  :text => { :format => "%s",
119
- :align => :left }
157
+ :align => :left },
158
+
159
+ :right_text => { :format => "%s",
160
+ :align => :right },
120
161
  }
121
162
  end
163
+
164
+ def filter_property(property)
165
+ property = property.to_s
166
+ case property
167
+ when /^page_header_/
168
+ property_key = property.sub('page_header_','').to_sym
169
+ [header,property_key]
170
+ when /^page_footer_/
171
+ property_key = property.sub('page_footer_','').to_sym
172
+ [footer,property_key]
173
+ when /^table_/
174
+ property_key = property.sub('table_','').to_sym
175
+ [table,property_key]
176
+ when /^column_/
177
+ property_key = property.sub('column_','').to_sym
178
+ [column,property_key]
179
+ else
180
+ [report,property.to_sym]
181
+ end
182
+ end
183
+
122
184
  end
123
185
  end
124
186
  end
@@ -28,7 +28,7 @@ module TnPDF
28
28
  end
29
29
 
30
30
  def boxes
31
- [left_box, center_box, right_box].compact
31
+ [left_box, center_box, right_box]
32
32
  end
33
33
 
34
34
  def top
@@ -42,13 +42,20 @@ module TnPDF
42
42
  def render(document, position)
43
43
  width ||= document.bounds.width
44
44
 
45
- box_width = width/(boxes.count)
45
+ box_width = width/(boxes.compact.count)
46
46
 
47
- boxes.inject(0) do |offset, box|
48
- x_pos = position[0] + offset
49
- y_pos = position[1] - top
50
- box.render(document, [x_pos, y_pos], box_width, height )
51
- offset += box_width
47
+ boxes.each do |box|
48
+ box.width ||= box_width
49
+ end
50
+
51
+ boxes_with_positions = [
52
+ boxes,
53
+ [position[0], (width-boxes[1].width)/2, width-boxes[2].width],
54
+ [position[1]-top]*3
55
+ ].transpose
56
+
57
+ boxes_with_positions.each do |box, x_pos, y_pos|
58
+ box.render(document, [x_pos, y_pos], height )
52
59
  end
53
60
  end
54
61
 
data/lib/tn_pdf/report.rb CHANGED
@@ -1,6 +1,118 @@
1
1
  module TnPDF
2
+ # @author Renato Zannon
3
+ # The {TnPDF::Report} class is the easier, best supported and more complete
4
+ # way to access the TnPDF API. It delegates most of it's work to fellow classes
5
+ # such as {TnPDF::Table} and {TnPDF::PageSection}, but the delegation
6
+ # itself and all the necessary setup is made under the hood.
7
+ # This means that everything the code needs to do is access methods such as
8
+ # {#record_collection} and {#table_columns}, and then it is forwarded to the
9
+ # appropriate member.
10
+ #
11
+ # For more specific needs, methods such as {#table} and {#page_header}, which
12
+ # give direct access to the report's members, are provided, so that the user can
13
+ # make low-level/bleeding edge adjustments without being restricted to this
14
+ # classe's interface.
15
+ #
16
+ # @attr [String] page_size
17
+ # The report's page size, in paper sizes such as "A4" and "Letter".
18
+ # Supports as much as much paper sizes as Prawn does.
19
+ #
20
+ # @attr [Symbol] page_layout
21
+ # The resulting pages' layout. Must be :landscape or :portrait
22
+ #
23
+ # @attr [Double] left_margin
24
+ # Sets the document's left margin. Accepts a Double value, defined in PDF
25
+ # points (1/72 inch) or a String "in" cm or mm, such as "1.5cm" and
26
+ # "50mm".
27
+ #
28
+ # @attr [Double] right_margin
29
+ # Sets the document's right margin. Accepts a Double value, defined in PDF
30
+ # points (1/72 inch) or a String "in" cm or mm, such as "1.5cm" and
31
+ # "50mm".
32
+ #
33
+ # @attr [Double] bottom_margin
34
+ # Sets the document's bottom margin. Accepts a Double value, defined in PDF
35
+ # points (1/72 inch) or a String "in" cm or mm, such as "1.5cm" and
36
+ # "50mm".
37
+ #
38
+ # @attr [Double] top_margin
39
+ # Sets the document's top margin. Accepts a Double value, defined in PDF
40
+ # points (1/72 inch) or a String "in" cm or mm, such as "1.5cm" and
41
+ # "50mm".
42
+ #
43
+ # @attr [String] font
44
+ # The default font to be used on the report. The only (currently) supported
45
+ # choices are "Helvetica" and "Courier", although Prawn's font embedding
46
+ # mechanism is a probable upcoming addition
47
+ #
48
+ # @attr [Fixnum] font_size
49
+ # The default report font size. In the usual "points" unit.
50
+ #
51
+ # @attr [String] images_path
52
+ # The path from where we will search for the requested images. Defaults to
53
+ # the current path, "./". In a Rails application, for instance, you would
54
+ # probably want to set this guy to RAILS_ROOT+"public/images"
55
+ #
56
+ # @attr [String, Array] text_before_table
57
+ # Some text to be rendered before the report's table. Can be used as some
58
+ # kind of prelude/explanation/introduction etc.
59
+ # It accepts a normal string, that will be rendered using the settings on
60
+ # {#font} and {#font_size}, or an Array containing a String and a Hash,
61
+ # the last being an hash of options, as accepted by Prawn's Document#text
62
+ # method.
63
+ # @example
64
+ # report.text_before_table = "Some text"
65
+ # @example
66
+ # report.text_before_table = ["Some text", :size => 20]
67
+ #
68
+ # @attr [String, Array] text_after_table
69
+ # Some text to be rendered after the report's table. Can be used as a
70
+ # confirmation, a conclusion, an acceptance term etc.
71
+ # It accepts a normal string, that will be rendered using the settings on
72
+ # {#font} and {#font_size}, or an Array containing a String and a Hash,
73
+ # the last being an hash of options, as accepted by Prawn's Document#text
74
+ # method.
75
+ # @example
76
+ # report.text_after_table = "Some text"
77
+ # @example
78
+ # report.text_after_table = ["Some text", :size => 20]
2
79
  class Report
3
- attr_reader :page_header, :page_footer, :table, :record_collection
80
+
81
+ # The underlying {PageSection} that represents the report pages' headers. Direct
82
+ # manipulation is disencouraged.
83
+ # @return [PageSection]
84
+ attr_reader :page_header
85
+
86
+ # The underlying {PageSection} that represents the report pages' footers. Direct
87
+ # manipulation is disencouraged.
88
+ # @return [PageSection]
89
+ attr_reader :page_footer
90
+
91
+ # The underlying {Table}. Direct manipulation is disencouraged, except in
92
+ # cases where fine adjustments are required, or when some (possibly
93
+ # bleeding-edge) functionality is not implemented on {Report} (yet).
94
+ # @return [Table]
95
+ attr_reader :table
96
+
97
+ def table
98
+ @table ||= Table.new(document)
99
+ end
100
+
101
+ # The underlying {Table}'s collection of objects. Each of these objects
102
+ # will be represented as a table row, by the application of the procedures
103
+ # described using {#table_columns=}. The order in which this property and
104
+ # {#table_columns} are called isn't an issue, as soon as *both* are set
105
+ # before {#render} is called.
106
+ # @return [Array]
107
+ attr_accessor :record_collection
108
+
109
+ def record_collection=(collection)
110
+ unless collection.kind_of? Array
111
+ raise ArgumentError, "collection should be an Array!"
112
+ end
113
+ @record_collection = table.collection = collection
114
+ end
115
+
4
116
  attr_accessor *Configuration.report_properties_names
5
117
 
6
118
  def initialize(properties = {})
@@ -39,24 +151,40 @@ module TnPDF
39
151
  forward_property("table", property)
40
152
  end
41
153
 
42
- def record_collection=(collection)
43
- unless collection.kind_of? Array
44
- raise ArgumentError, "collection should be an Array!"
45
- end
46
- @record_collection = table.collection = collection
47
- end
48
-
154
+ # The underlying {Table}'s collection of columns. Refer to
155
+ # {Table#add_column} for reference on the Array's structure.
156
+ # @return [Array]
49
157
  def table_columns
50
158
  table.columns || Array.new
51
159
  end
52
160
 
161
+ # Forwards the received arguments to {Table}. Refer to {Table#add_column}
162
+ # for a reference on the valid structure.
163
+ # Be advised that calling this method *resets* the columns set previously,
164
+ # insted of just adding new ones.
165
+ # @param [Array] columns An Array of elements as accepted by {Table#add_column}
166
+ # @raise [ArgumentError] When the argument isn't an Array
53
167
  def table_columns=(columns)
54
168
  raise ArgumentError unless columns.kind_of? Array
169
+ table.reset_columns
55
170
  columns.each do |column|
56
171
  table.add_column column
57
172
  end
58
173
  end
59
174
 
175
+ # Forwards the arguments to the underlying {Table}. Refer to
176
+ # {Table#add_footer Table#add_footer} for a full description of the
177
+ # required syntax
178
+ def add_table_footer(row=nil, &block)
179
+ table.add_footer(row, &block)
180
+ end
181
+
182
+ # Renders the report on filename. This method also calls render on the
183
+ # report's members - {#table}, {#page_header} and {#page_footer} - which means
184
+ # that, on a straightforward report, the process is reduced to set up {#table_columns}
185
+ # and {#record_collection}, and then calling {#render}. This alone already does all the
186
+ # trick to generate the PDF.
187
+ # @param [String, IO] filename The file on which the report will render
60
188
  def render(filename)
61
189
  document_width = document.bounds.width
62
190
  page_header_position = [0, document.cursor]
@@ -71,9 +199,10 @@ module TnPDF
71
199
  end
72
200
 
73
201
  table_height = page_body_height
74
- document.bounding_box([0, page_body_height+page_footer.total_height],
202
+
203
+ document.bounding_box([0, document.cursor],
75
204
  :width => document.bounds.width) do
76
- table.render(document, table_height)
205
+ table.render(table_height)
77
206
  end
78
207
 
79
208
  document.repeat :all do
@@ -83,21 +212,15 @@ module TnPDF
83
212
  document.render_file filename
84
213
  end
85
214
 
86
- def page_body_height
87
- height = document.bounds.height
88
- height -= page_header.total_height
89
- height -= page_footer.total_height
90
- end
91
-
215
+ # The underlying {Prawn::Document}. Direct manipulation is highly
216
+ # disencouraged, except for querying information.
217
+ # @return [Prawn::Document]
218
+ attr_reader :document
92
219
  def document
93
220
  @document ||= Prawn::Document.new(properties)
94
221
  end
95
222
 
96
- def table
97
- @table ||= Table.new
98
- end
99
-
100
- # Configurable properties
223
+ private
101
224
 
102
225
  def properties
103
226
  Configuration.report_properties_names.inject({}) do |properties_hash, property|
@@ -106,7 +229,11 @@ module TnPDF
106
229
  end
107
230
  end
108
231
 
109
- private
232
+ def page_body_height
233
+ height = document.bounds.height
234
+ height -= page_header.total_height
235
+ height -= page_footer.total_height
236
+ end
110
237
 
111
238
  def initialize_properties(properties)
112
239
  owned_properties = Configuration.report_properties_names