prawn 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -2
  3. data/lib/prawn.rb +5 -3
  4. data/lib/prawn/document.rb +7 -7
  5. data/lib/prawn/document/bounding_box.rb +1 -1
  6. data/lib/prawn/document/column_box.rb +3 -3
  7. data/lib/prawn/document/internals.rb +1 -1
  8. data/lib/prawn/font.rb +24 -8
  9. data/lib/prawn/font/afm.rb +4 -4
  10. data/lib/prawn/font_metric_cache.rb +1 -1
  11. data/lib/prawn/grid.rb +3 -1
  12. data/lib/prawn/image_handler.rb +3 -1
  13. data/lib/prawn/images/jpg.rb +1 -1
  14. data/lib/prawn/measurement_extensions.rb +1 -1
  15. data/lib/prawn/outline.rb +3 -1
  16. data/lib/prawn/security.rb +1 -1
  17. data/lib/prawn/security/arcfour.rb +4 -2
  18. data/lib/prawn/table.rb +14 -2
  19. data/lib/prawn/table/cell.rb +1 -1
  20. data/lib/prawn/table/cells.rb +1 -50
  21. data/lib/prawn/table/column_width_calculator.rb +131 -10
  22. data/lib/prawn/text.rb +1 -1
  23. data/lib/prawn/text/formatted.rb +2 -0
  24. data/lib/prawn/text/formatted/arranger.rb +1 -1
  25. data/lib/prawn/text/formatted/box.rb +5 -5
  26. data/lib/prawn/text/formatted/line_wrap.rb +1 -1
  27. data/lib/prawn/text/formatted/wrap.rb +2 -0
  28. data/lib/prawn/utilities.rb +3 -3
  29. data/manual/absolute_position.pdf +0 -0
  30. data/manual/basic_concepts/adding_pages.rb +3 -3
  31. data/manual/basic_concepts/basic_concepts.rb +6 -6
  32. data/manual/basic_concepts/cursor.rb +5 -5
  33. data/manual/basic_concepts/measurement.rb +2 -2
  34. data/manual/basic_concepts/origin.rb +3 -3
  35. data/manual/basic_concepts/other_cursor_helpers.rb +6 -6
  36. data/manual/bounding_box/bounding_box.rb +6 -6
  37. data/manual/bounding_box/bounds.rb +6 -6
  38. data/manual/bounding_box/canvas.rb +2 -2
  39. data/manual/bounding_box/creation.rb +3 -3
  40. data/manual/bounding_box/indentation.rb +9 -9
  41. data/manual/bounding_box/nesting.rb +8 -8
  42. data/manual/bounding_box/russian_boxes.rb +1 -1
  43. data/manual/bounding_box/stretchy.rb +8 -8
  44. data/manual/{manual/manual.rb → contents.rb} +6 -9
  45. data/manual/{manual/cover.rb → cover.rb} +6 -6
  46. data/manual/document_and_page_options/background.rb +2 -2
  47. data/manual/document_and_page_options/document_and_page_options.rb +4 -4
  48. data/manual/document_and_page_options/metadata.rb +2 -2
  49. data/manual/document_and_page_options/page_margins.rb +2 -2
  50. data/manual/document_and_page_options/page_size.rb +3 -3
  51. data/manual/example_helper.rb +5 -409
  52. data/manual/graphics/circle_and_ellipse.rb +4 -4
  53. data/manual/graphics/color.rb +4 -4
  54. data/manual/graphics/common_lines.rb +5 -5
  55. data/manual/graphics/fill_and_stroke.rb +5 -5
  56. data/manual/graphics/fill_rules.rb +1 -1
  57. data/manual/graphics/gradients.rb +1 -1
  58. data/manual/graphics/graphics.rb +8 -8
  59. data/manual/graphics/helper.rb +1 -1
  60. data/manual/graphics/line_width.rb +5 -5
  61. data/manual/graphics/lines_and_curves.rb +5 -5
  62. data/manual/graphics/polygon.rb +4 -4
  63. data/manual/graphics/rectangle.rb +3 -3
  64. data/manual/graphics/rotate.rb +5 -5
  65. data/manual/graphics/scale.rb +5 -5
  66. data/manual/graphics/soft_masks.rb +1 -1
  67. data/manual/graphics/stroke_cap.rb +3 -3
  68. data/manual/graphics/stroke_dash.rb +1 -1
  69. data/manual/graphics/stroke_join.rb +4 -4
  70. data/manual/graphics/translate.rb +5 -5
  71. data/manual/graphics/transparency.rb +5 -5
  72. data/manual/{manual/how_to_read_this_manual.rb → how_to_read_this_manual.rb} +16 -17
  73. data/manual/images/absolute_position.rb +3 -3
  74. data/manual/images/fit.rb +2 -2
  75. data/manual/images/horizontal.rb +3 -3
  76. data/manual/images/images.rb +7 -7
  77. data/manual/images/plain_image.rb +1 -1
  78. data/manual/images/scale.rb +3 -3
  79. data/manual/images/vertical.rb +3 -3
  80. data/manual/images/width_and_height.rb +3 -3
  81. data/manual/layout/boxes.rb +4 -4
  82. data/manual/layout/content.rb +3 -3
  83. data/manual/layout/layout.rb +5 -5
  84. data/manual/layout/simple_grid.rb +2 -2
  85. data/manual/outline/add_subsection_to.rb +7 -7
  86. data/manual/outline/insert_section_after.rb +5 -5
  87. data/manual/outline/outline.rb +6 -6
  88. data/manual/outline/sections_and_pages.rb +9 -9
  89. data/manual/repeatable_content/page_numbering.rb +3 -3
  90. data/manual/repeatable_content/repeatable_content.rb +5 -5
  91. data/manual/repeatable_content/repeater.rb +4 -4
  92. data/manual/repeatable_content/stamp.rb +4 -4
  93. data/manual/security/encryption.rb +2 -2
  94. data/manual/security/permissions.rb +2 -2
  95. data/manual/security/security.rb +5 -5
  96. data/manual/table/basic_block.rb +5 -5
  97. data/manual/table/before_rendering_page.rb +1 -1
  98. data/manual/table/cell_border_lines.rb +4 -4
  99. data/manual/table/cell_borders_and_bg.rb +4 -4
  100. data/manual/table/cell_dimensions.rb +3 -3
  101. data/manual/table/cell_text.rb +6 -6
  102. data/manual/table/column_widths.rb +4 -4
  103. data/manual/table/content_and_subtables.rb +5 -5
  104. data/manual/table/creation.rb +2 -2
  105. data/manual/table/filtering.rb +6 -6
  106. data/manual/table/flow_and_header.rb +2 -2
  107. data/manual/table/image_cells.rb +1 -1
  108. data/manual/table/position.rb +1 -1
  109. data/manual/table/row_colors.rb +3 -3
  110. data/manual/table/span.rb +1 -1
  111. data/manual/table/style.rb +3 -3
  112. data/manual/table/table.rb +7 -7
  113. data/manual/table/width.rb +3 -3
  114. data/manual/text/alignment.rb +1 -1
  115. data/manual/text/color.rb +1 -1
  116. data/manual/text/column_box.rb +1 -1
  117. data/manual/text/fallback_fonts.rb +3 -3
  118. data/manual/text/font.rb +7 -7
  119. data/manual/text/font_size.rb +9 -9
  120. data/manual/text/font_style.rb +3 -3
  121. data/manual/text/formatted_callbacks.rb +3 -3
  122. data/manual/text/formatted_text.rb +3 -3
  123. data/manual/text/free_flowing_text.rb +6 -6
  124. data/manual/text/inline.rb +5 -5
  125. data/manual/text/kerning_and_character_spacing.rb +7 -7
  126. data/manual/text/leading.rb +4 -4
  127. data/manual/text/line_wrapping.rb +4 -4
  128. data/manual/text/paragraph_indentation.rb +3 -3
  129. data/manual/text/positioned_text.rb +3 -3
  130. data/manual/text/registering_families.rb +6 -6
  131. data/manual/text/rendering_and_color.rb +2 -2
  132. data/manual/text/right_to_left_text.rb +2 -2
  133. data/manual/text/rotation.rb +4 -4
  134. data/manual/text/single_usage.rb +3 -3
  135. data/manual/text/text.rb +9 -9
  136. data/manual/text/text_box_excess.rb +2 -2
  137. data/manual/text/text_box_extensions.rb +3 -3
  138. data/manual/text/text_box_overflow.rb +4 -4
  139. data/manual/text/utf8.rb +5 -5
  140. data/manual/text/win_ansi_charset.rb +1 -1
  141. data/prawn.gemspec +5 -3
  142. data/spec/acceptance/png.rb +5 -3
  143. data/spec/cell_spec.rb +1 -0
  144. data/spec/column_box_spec.rb +1 -1
  145. data/spec/extensions/encoding_helpers.rb +2 -0
  146. data/spec/extensions/mocha.rb +2 -0
  147. data/spec/font_spec.rb +15 -0
  148. data/spec/measurement_units_spec.rb +2 -0
  149. data/spec/repeater_spec.rb +2 -0
  150. data/spec/soft_mask_spec.rb +2 -0
  151. data/spec/stamp_spec.rb +2 -0
  152. data/spec/table_spec.rb +103 -10
  153. data/spec/text_spec.rb +1 -1
  154. data/spec/transparency_spec.rb +2 -0
  155. metadata +42 -18
  156. data/lib/prawn/layout.rb +0 -17
  157. data/manual/example_file.rb +0 -111
  158. data/manual/example_package.rb +0 -53
  159. data/manual/example_section.rb +0 -46
  160. data/manual/syntax_highlight.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: edf9e637f24e5c4554e995712cb589f0b21737fb
4
- data.tar.gz: 97750f73864c853ff874f8a5a0f7984a6f9d8279
3
+ metadata.gz: 8492b319da06bf93faa4e58cfa4932b46fd8a474
4
+ data.tar.gz: ba94547a9eda37f13c91b7293203a12df99b9815
5
5
  SHA512:
6
- metadata.gz: bd80b65ac0360fa6786b6bffacfc34a87511cc8d79e260dfd8db78b86a5003182a41d5ad559e5c1403724273c83b71b29d426c31a422fd7b1e5986a859ac1a5d
7
- data.tar.gz: 4b63fc53c8481155ed173cb3e4b8146ac052173e0bd91f4fa27aece66525ad5f6968a48230addbb5ae2a043a3bc33829c79c9447a8c734f3bd2da45468b913da
6
+ metadata.gz: d898304c9f63ac7826f49b3802106ec84e36fb8a0efcb07d85b2b88d0e7a2f3016f722655fea9e4992e028a8a740ef5d29a5ee9e7287164f6768d9435179891c
7
+ data.tar.gz: 0127f4e217c2588fe196665712cb1fe7e3914a49d3ba4e0b248e1a7bd3615f52e6c97e2288c4c7fb9295383e48c03ad48557c137e8d1f822841c65d1a8f77d62
data/Rakefile CHANGED
@@ -5,8 +5,9 @@ require 'rake'
5
5
  require 'rspec/core/rake_task'
6
6
  require 'yard'
7
7
  require 'rubygems/package_task'
8
+ require 'rubocop/rake_task'
8
9
 
9
- task :default => [:spec]
10
+ task :default => [:rubocop, :spec]
10
11
 
11
12
  desc "Run all rspec files"
12
13
  RSpec::Core::RakeTask.new("spec") do |c|
@@ -31,7 +32,7 @@ desc "Generate the 'Prawn by Example' manual"
31
32
  task :manual do
32
33
  puts "Building manual..."
33
34
  require File.expand_path(File.join(File.dirname(__FILE__),
34
- %w[manual manual manual]))
35
+ %w[manual contents]))
35
36
  puts "The Prawn manual is available at manual.pdf. Happy Prawning!"
36
37
  end
37
38
 
@@ -52,3 +53,4 @@ task :console do
52
53
  IRB.start
53
54
  end
54
55
 
56
+ Rubocop::RakeTask.new
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  # Welcome to Prawn, the best PDF Generation library ever.
2
4
  # This documentation covers user level functionality.
3
5
  #
@@ -7,7 +9,7 @@ require 'ttfunk'
7
9
  require "pdf/core"
8
10
 
9
11
  module Prawn
10
- VERSION = "1.0.0"
12
+ VERSION = "1.1.0"
11
13
 
12
14
  extend self
13
15
 
@@ -20,7 +22,7 @@ module Prawn
20
22
  #
21
23
  BASEDIR = File.expand_path(File.join(dir, '..'))
22
24
  DATADIR = File.expand_path(File.join(dir, '..', 'data'))
23
-
25
+
24
26
  FLOAT_PRECISION = 1.0e-9
25
27
 
26
28
  # Whe set to true, Prawn will verify hash options to ensure only valid keys
@@ -81,7 +83,7 @@ require_relative "prawn/encoding"
81
83
  require_relative "prawn/measurements"
82
84
  require_relative "prawn/repeater"
83
85
  require_relative "prawn/outline"
84
- require_relative "prawn/layout"
86
+ require_relative "prawn/grid"
85
87
 
86
88
  require_relative "prawn/image_handler"
87
89
 
@@ -97,7 +97,7 @@ module Prawn
97
97
  end
98
98
 
99
99
  # @private
100
- def self.inherited(base)
100
+ def self.inherited(base)
101
101
  extensions.each { |e| base.extensions << e }
102
102
  end
103
103
 
@@ -148,7 +148,7 @@ module Prawn
148
148
  # Creates a new PDF Document. The following options are available (with
149
149
  # the default values marked in [])
150
150
  #
151
- # <tt>:page_size</tt>:: One of the Document::PageGeometry sizes [LETTER]
151
+ # <tt>:page_size</tt>:: One of the PDF::Core::PageGeometry sizes [LETTER]
152
152
  # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
153
153
  # <tt>:margin</tt>:: Sets the margin on all sides in points [0.5 inch]
154
154
  # <tt>:left_margin</tt>:: Sets the left margin in points [0.5 inch]
@@ -587,7 +587,7 @@ module Prawn
587
587
  #
588
588
  # @private
589
589
  def group(*a, &b)
590
- raise NotImplementedError,
590
+ raise NotImplementedError,
591
591
  "Document#group has been disabled because its implementation "+
592
592
  "lead to corrupted documents whenever a page boundary was "+
593
593
  "crossed. We will try to work on reimplementing it in a "+
@@ -596,7 +596,7 @@ module Prawn
596
596
 
597
597
  # @private
598
598
  def transaction
599
- raise NotImplementedError,
599
+ raise NotImplementedError,
600
600
  "Document#transaction has been disabled because its implementation "+
601
601
  "lead to corrupted documents whenever a page boundary was "+
602
602
  "crossed. We will try to work on reimplementing it in a "+
@@ -629,8 +629,8 @@ module Prawn
629
629
  end
630
630
 
631
631
  # @private
632
-
633
- def mask(*fields)
632
+
633
+ def mask(*fields)
634
634
  # Stores the current state of the named attributes, executes the block, and
635
635
  # then restores the original values after the block has executed.
636
636
  # -- I will remove the nodoc if/when this feature is a little less hacky
@@ -696,7 +696,7 @@ module Prawn
696
696
 
697
697
  # we must update bounding box if not flowing from the previous page
698
698
  #
699
- @bounding_box = @margin_box unless @bounding_box && @bounding_box.parent
699
+ @bounding_box = @margin_box unless @bounding_box && @bounding_box.parent
700
700
  end
701
701
 
702
702
  def apply_margin_options(options)
@@ -199,7 +199,7 @@ module Prawn
199
199
  # If the user actions did not modify the y position
200
200
  # restore the original y position before the bounding
201
201
  # box was created.
202
-
202
+
203
203
  if y == @bounding_box.absolute_top
204
204
  self.y = original_ypos
205
205
  end
@@ -20,12 +20,12 @@ module Prawn
20
20
  #
21
21
  # column_box accepts the same parameters as bounding_box, as well as the
22
22
  # number of :columns and a :spacer (in points) between columns. If resetting
23
- # the top margin is desired on a new page (e.g. to allow for initial page
23
+ # the top margin is desired on a new page (e.g. to allow for initial page
24
24
  # wide column titles) the option :reflow_margins => true can be set.
25
25
  #
26
- # Defaults are :columns = 3, :spacer = font_size, and
26
+ # Defaults are :columns = 3, :spacer = font_size, and
27
27
  # :reflow_margins => false
28
- #
28
+ #
29
29
  # Under PDF::Writer, "spacer" was known as "gutter"
30
30
  #
31
31
  def column_box(*args, &block)
@@ -15,7 +15,7 @@ module Prawn
15
15
  # are you won't need anything you find here.
16
16
  #
17
17
  # @private
18
- module Internals
18
+ module Internals
19
19
  # Creates a new Prawn::Reference and adds it to the Document's object
20
20
  # list. The +data+ argument is anything that Prawn::PdfObject() can convert.
21
21
  #
@@ -237,7 +237,15 @@ module Prawn
237
237
  end
238
238
  end
239
239
  key = "#{name}:#{options[:font] || 0}"
240
- font_registry[key] ||= Font.load(self, name, options.merge(:family => family))
240
+
241
+ if name.is_a? Prawn::Font
242
+ font_registry[key] = name
243
+ else
244
+ font_registry[key] ||= Font.load( self,
245
+ name,
246
+ options.merge(family: family)
247
+ )
248
+ end
241
249
  end
242
250
 
243
251
  # Hash of Font objects keyed by names
@@ -279,13 +287,21 @@ module Prawn
279
287
  # Shortcut interface for constructing a font object. Filenames of the form
280
288
  # *.ttf will call Font::TTF.new, *.dfont Font::DFont.new, and anything else
281
289
  # will be passed through to Font::AFM.new()
282
- #
283
- def self.load(document,name,options={})
284
- case name.to_s
285
- when /\.ttf$/i then TTF.new(document, name, options)
286
- when /\.dfont$/i then DFont.new(document, name, options)
287
- when /\.afm$/i then AFM.new(document, name, options)
288
- else AFM.new(document, name, options)
290
+ def self.load(document, src, options={})
291
+ case font_format(src, options)
292
+ when 'ttf' then TTF.new(document, src, options)
293
+ when 'dfont' then DFont.new(document, src, options)
294
+ else AFM.new(document, src, options)
295
+ end
296
+ end
297
+
298
+ def self.font_format(src, options)
299
+ return options.fetch(:format, 'ttf') if src.respond_to? :read
300
+
301
+ case src.to_s
302
+ when /\.ttf$/i then return 'ttf'
303
+ when /\.dfont$/i then return 'dfont'
304
+ else return 'afm'
289
305
  end
290
306
  end
291
307
 
@@ -157,7 +157,7 @@ module Prawn
157
157
  end
158
158
 
159
159
  def parse_afm(file_name)
160
- data = {:glyph_widths => {}, :bounding_boxes => {}, :kern_pairs => {}, :attributes => {}}
160
+ data = {:glyph_widths => {}, :bounding_boxes => {}, :kern_pairs => {}, :attributes => {}}
161
161
  section = []
162
162
 
163
163
  File.foreach(file_name) do |line|
@@ -187,7 +187,7 @@ module Prawn
187
187
  parse_generic_afm_attribute(line, data)
188
188
  end
189
189
  end
190
-
190
+
191
191
  # process data parsed from AFM file to build tables which
192
192
  # will be used when measuring and kerning text
193
193
  data[:glyph_table] = (0..255).map do |i|
@@ -234,9 +234,9 @@ module Prawn
234
234
  e.respond_to?(:force_encoding) ? e.force_encoding(::Encoding::Windows_1252) : e
235
235
  }
236
236
  end
237
-
237
+
238
238
  private
239
-
239
+
240
240
  def unscaled_width_of(string)
241
241
  string.bytes.inject(0) do |s,r|
242
242
  s + @glyph_table[r]
@@ -13,7 +13,7 @@ module Prawn
13
13
  # of various strings for layout purposes.
14
14
  #
15
15
  # @private
16
- class FontMetricCache
16
+ class FontMetricCache
17
17
 
18
18
  CacheEntry = Struct.new( :font, :options, :string )
19
19
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  # grid.rb: Provides a basic grid layout system for Prawn
2
4
  #
3
5
  # Contributed by Andrew O'Brien in March 2009
@@ -14,7 +16,7 @@ module Prawn
14
16
  #
15
17
  # Note that a completely new grid object is built each time define_grid()
16
18
  # is called. This means that all subsequent calls to grid() will use
17
- # the newly defined Grid object -- grids are not nestable like
19
+ # the newly defined Grid object -- grids are not nestable like
18
20
  # bounding boxes are.
19
21
 
20
22
  def define_grid(options = {})
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  # ImageHandler provides a way to register image processors with Prawn
2
4
  #
3
5
  # Contributed by Evan Sharp in November 2013.
@@ -6,7 +8,7 @@
6
8
 
7
9
  module Prawn
8
10
  # @group Extension API
9
-
11
+
10
12
  def self.image_handler
11
13
  @image_handler ||= ImageHandler.new
12
14
  end
@@ -16,7 +16,7 @@ module Prawn
16
16
  #
17
17
  class JPG < Image
18
18
  # @group Extension API
19
-
19
+
20
20
  attr_reader :width, :height, :bits, :channels
21
21
  attr_accessor :scaled_width, :scaled_height
22
22
 
@@ -15,7 +15,7 @@ class Numeric
15
15
  # 72 points per inch
16
16
 
17
17
  # @group Experimental API
18
-
18
+
19
19
  def mm
20
20
  return mm2pt(self)
21
21
  end
@@ -1,7 +1,9 @@
1
+ # encoding: utf-8
2
+
1
3
  module Prawn
2
4
  class Document
3
5
  # @group Stable API
4
-
6
+
5
7
  # Lazily instantiates a Prawn::Outline object for document. This is used as point of entry
6
8
  # to methods to build the outline tree for a document's table of contents.
7
9
  def outline
@@ -213,7 +213,7 @@ module PDF
213
213
  # from the indirect object referencing obj.
214
214
  #
215
215
  # @private
216
- def EncryptedPdfObject(obj, key, id, gen, in_content_stream=false)
216
+ def EncryptedPdfObject(obj, key, id, gen, in_content_stream=false)
217
217
  case obj
218
218
  when Array
219
219
  "[" << obj.map { |e|
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  # Implementation of the "ARCFOUR" algorithm ("alleged RC4 (tm)"). Implemented
2
4
  # as described at:
3
5
  # http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt
@@ -17,7 +19,7 @@ class Arcfour
17
19
  # 1. Allocate an 256 element array of 8 bit bytes to be used as an S-box
18
20
  # 2. Initialize the S-box. Fill each entry first with it's index
19
21
  @sbox = (0..255).to_a
20
-
22
+
21
23
  # 3. Fill another array of the same size (256) with the key, repeating
22
24
  # bytes as necessary.
23
25
  s2 = []
@@ -39,7 +41,7 @@ class Arcfour
39
41
  def encrypt(string)
40
42
  string.unpack('c*').map{|byte| byte ^ key_byte}.pack('c*')
41
43
  end
42
-
44
+
43
45
  private
44
46
 
45
47
  # Produces the next byte of key material in the stream (3.2 Stream Generation)
@@ -43,6 +43,16 @@ module Prawn
43
43
 
44
44
  end
45
45
 
46
+ module Errors
47
+ # This error is raised when table data is malformed
48
+ #
49
+ InvalidTableData = Class.new(StandardError)
50
+
51
+ # This error is raised when an empty or nil table is rendered
52
+ #
53
+ EmptyTable = Class.new(StandardError)
54
+ end
55
+
46
56
  # Next-generation table drawing for Prawn.
47
57
  #
48
58
  # = Data
@@ -320,7 +330,9 @@ module Prawn
320
330
  c = Cells.new(cells_this_page.map { |ci, _| ci })
321
331
  @before_rendering_page.call(c)
322
332
  end
323
- Cell.draw_cells(cells_this_page)
333
+ if @header_row.nil? || cells_this_page.size > @header_row.size
334
+ Cell.draw_cells(cells_this_page)
335
+ end
324
336
  cells_this_page = []
325
337
 
326
338
  # start a new page or column
@@ -527,7 +539,7 @@ module Prawn
527
539
  rows_to_operate_on = @header_row.rows(row_of_header) if row_of_header
528
540
  rows_to_operate_on.each do |cell|
529
541
  cell.row = row
530
- cell.dummy_cells.each {|c| c.row = row }
542
+ cell.dummy_cells.each {|c| c.row = row + c.row }
531
543
  page_of_cells << [cell, [cell.x + x_offset, y]]
532
544
  end
533
545
  rows_to_operate_on.height
@@ -153,7 +153,7 @@ module Prawn
153
153
  # this span group. They know their own width / height, but do not draw
154
154
  # anything.
155
155
  #
156
- attr_reader :dummy_cells
156
+ attr_reader :dummy_cells
157
157
 
158
158
  # Instantiates a Cell based on the given options. The particular class of
159
159
  # cell returned depends on the :content argument. See the Prawn::Table
@@ -228,56 +228,7 @@ module Prawn
228
228
  # each cell, grouped by +row_or_column+.
229
229
  #
230
230
  def aggregate_cell_values(row_or_column, meth, aggregate)
231
- values = {}
232
-
233
- #calculate values for all cells that do not span accross multiple cells
234
- #this ensures that we don't have a problem if the first line includes
235
- #a cell that spans across multiple cells
236
- each do |cell|
237
- #don't take spanned cells
238
- if cell.colspan == 1 and cell.class != Prawn::Table::Cell::SpanDummy
239
- index = cell.send(row_or_column)
240
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
241
- end
242
- end
243
-
244
- #if there are only colspanned or rowspanned cells in a table
245
- spanned_width_needs_fixing = true
246
-
247
- each do |cell|
248
- index = cell.send(row_or_column)
249
- if cell.colspan > 1
250
- #calculate current (old) return value before we do anything
251
- old_sum = 0
252
- cell.colspan.times { |i|
253
- old_sum += values[index+i] unless values[index+i].nil?
254
- }
255
-
256
- #calculate future return value
257
- new_sum = cell.send(meth) * cell.colspan
258
-
259
- #due to float rounding errors we need to ignore a small difference in the new
260
- #and the old sum the same had to be done in
261
- #the column_width_calculator#natural_width
262
- spanned_width_needs_fixing = ((new_sum - old_sum) > Prawn::FLOAT_PRECISION)
263
-
264
- if spanned_width_needs_fixing
265
- #not entirely sure why we need this line, but with it the tests pass
266
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
267
- #overwrite the old values with the new ones, but only if all entries existed
268
- entries_exist = true
269
- cell.colspan.times { |i| entries_exist = false if values[index+i].nil? }
270
- cell.colspan.times { |i|
271
- values[index+i] = cell.send(meth) if entries_exist
272
- }
273
- end
274
- else
275
- if spanned_width_needs_fixing && cell.class == Prawn::Table::Cell::SpanDummy
276
- values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
277
- end
278
- end
279
- end
280
- values.values.inject(0, &:+)
231
+ ColumnWidthCalculator.new(self).aggregate_cell_values(row_or_column, meth, aggregate)
281
232
  end
282
233
 
283
234
  # Transforms +spec+, a column / row specification, into an object that
@@ -1,48 +1,104 @@
1
+ # encoding: utf-8
2
+
1
3
  module Prawn
2
4
  class Table
3
5
  # @private
4
- class ColumnWidthCalculator
6
+ class ColumnWidthCalculator
5
7
  def initialize(cells)
6
8
  @cells = cells
7
9
 
8
10
  @widths_by_column = Hash.new(0)
9
11
  @rows_with_a_span_dummy = Hash.new(false)
10
- end
11
12
 
12
- def natural_widths
13
+ #calculate for each row if it includes a Cell:SpanDummy
13
14
  @cells.each do |cell|
14
15
  @rows_with_a_span_dummy[cell.row] = true if cell.is_a?(Cell::SpanDummy)
15
16
  end
17
+ end
18
+
19
+ # does this row include a Cell:SpanDummy?
20
+ #
21
+ # @param row - the row that should be checked for Cell:SpanDummy elements
22
+ #
23
+ def has_a_span_dummy?(row)
24
+ @rows_with_a_span_dummy[row]
25
+ end
26
+
27
+ # helper method
28
+ # column widths are stored in the values array
29
+ # a cell may span cells whose value is only partly given
30
+ # this function handles this special case
31
+ #
32
+ # @param values - The columns widths calculated up until now
33
+ # @param cell - The current cell
34
+ # @param index - The current column
35
+ # @param meth - Meth (min/max); used to calculate values to be filled
36
+ #
37
+ def fill_values_if_needed(values, cell, index, meth)
38
+ #have all spanned indices been filled with a value?
39
+ #e.g. values[0], values[1] and values[2] don't return nil given a index of 0 and a colspan of 3
40
+ number_of_nil_values = 0
41
+ cell.colspan.times do |i|
42
+ number_of_nil_values += 1 if values[index+i].nil?
43
+ end
44
+
45
+ #nothing to do? because
46
+ #a) all values are filled
47
+ return values if number_of_nil_values == 0
48
+ #b) no values are filled
49
+ return values if number_of_nil_values == cell.colspan
50
+ #c) I am not sure why this line is needed FIXXME
51
+ #some test cases manage to this line even though there is no dummy cell in the row
52
+ #I'm not sure if this is a sign for a further underlying bug.
53
+ return values unless has_a_span_dummy?(cell.row)
54
+ #fill up the values array
55
+
56
+ #calculate the new sum
57
+ new_sum = cell.send(meth) * cell.colspan
58
+ #substract any calculated values
59
+ cell.colspan.times do |i|
60
+ new_sum -= values[index+i] unless values[index+i].nil?
61
+ end
62
+
63
+ #calculate value for the remaining - not yet filled - cells.
64
+ new_value = new_sum.to_f / number_of_nil_values
65
+ #fill the not yet filled cells
66
+ cell.colspan.times do |i|
67
+ values[index+i] = new_value if values[index+i].nil?
68
+ end
69
+ return values
70
+ end
16
71
 
72
+ def natural_widths
17
73
  #calculate natural column width for all rows that do not include a span dummy
18
74
  @cells.each do |cell|
19
- unless @rows_with_a_span_dummy[cell.row]
20
- @widths_by_column[cell.column] =
75
+ unless has_a_span_dummy?(cell.row)
76
+ @widths_by_column[cell.column] =
21
77
  [@widths_by_column[cell.column], cell.width.to_f].max
22
78
  end
23
79
  end
24
80
 
25
81
  #integrate natural column widths for all rows that do include a span dummy
26
82
  @cells.each do |cell|
27
- next unless @rows_with_a_span_dummy[cell.row]
83
+ next unless has_a_span_dummy?(cell.row)
28
84
  #the width of a SpanDummy cell will be calculated by the "mother" cell
29
85
  next if cell.is_a?(Cell::SpanDummy)
30
86
 
31
87
  if cell.colspan == 1
32
- @widths_by_column[cell.column] =
88
+ @widths_by_column[cell.column] =
33
89
  [@widths_by_column[cell.column], cell.width.to_f].max
34
90
  else
35
91
  #calculate the current with of all cells that will be spanned by the current cell
36
- current_width_of_spanned_cells =
92
+ current_width_of_spanned_cells =
37
93
  @widths_by_column.to_a[cell.column..(cell.column + cell.colspan - 1)]
38
94
  .collect{|key, value| value}.inject(0, :+)
39
95
 
40
96
  #update the Hash only if the new with is at least equal to the old one
41
97
  #due to arithmetic errors we need to ignore a small difference in the new and the old sum
42
98
  #the same had to be done in the column_widht_calculator#natural_width
43
- update_hash = ((cell.width.to_f - current_width_of_spanned_cells) >
99
+ update_hash = ((cell.width.to_f - current_width_of_spanned_cells) >
44
100
  Prawn::FLOAT_PRECISION)
45
-
101
+
46
102
  if update_hash
47
103
  # Split the width of colspanned cells evenly by columns
48
104
  width_per_column = cell.width.to_f / cell.colspan
@@ -56,6 +112,71 @@ module Prawn
56
112
 
57
113
  @widths_by_column.sort_by { |col, _| col }.map { |_, w| w }
58
114
  end
115
+
116
+ # get column widths (either min or max depending on meth)
117
+ # used in cells.rb
118
+ #
119
+ # @param row_or_column - you may call this on either rows or columns
120
+ # @param meth - min/max
121
+ # @param aggregate - functions from cell.rb to be used to aggregate e.g. avg_spanned_min_width
122
+ #
123
+ def aggregate_cell_values(row_or_column, meth, aggregate)
124
+ values = {}
125
+
126
+ #calculate values for all cells that do not span accross multiple cells
127
+ #this ensures that we don't have a problem if the first line includes
128
+ #a cell that spans across multiple cells
129
+ @cells.each do |cell|
130
+ #don't take spanned cells
131
+ if cell.colspan == 1 and cell.class != Prawn::Table::Cell::SpanDummy
132
+ index = cell.send(row_or_column)
133
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
134
+ end
135
+ end
136
+
137
+ # if there are only colspanned or rowspanned cells in a table
138
+ spanned_width_needs_fixing = true
139
+
140
+ @cells.each do |cell|
141
+ index = cell.send(row_or_column)
142
+ if cell.colspan > 1
143
+ #special treatment if some but not all spanned indices in the values array have been calculated
144
+ #only applies to rows
145
+ values = fill_values_if_needed(values, cell, index, meth) if row_or_column == :column
146
+ #calculate current (old) return value before we do anything
147
+ old_sum = 0
148
+ cell.colspan.times { |i|
149
+ old_sum += values[index+i] unless values[index+i].nil?
150
+ }
151
+
152
+ #calculate future return value
153
+ new_sum = cell.send(meth) * cell.colspan
154
+
155
+ #due to float rounding errors we need to ignore a small difference in the new
156
+ #and the old sum the same had to be done in
157
+ #the column_width_calculator#natural_width
158
+ spanned_width_needs_fixing = ((new_sum - old_sum) > Prawn::FLOAT_PRECISION)
159
+
160
+ if spanned_width_needs_fixing
161
+ #not entirely sure why we need this line, but with it the tests pass
162
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
163
+ #overwrite the old values with the new ones, but only if all entries existed
164
+ entries_exist = true
165
+ cell.colspan.times { |i| entries_exist = false if values[index+i].nil? }
166
+ cell.colspan.times { |i|
167
+ values[index+i] = cell.send(meth) if entries_exist
168
+ }
169
+ end
170
+ else
171
+ if spanned_width_needs_fixing && cell.class == Prawn::Table::Cell::SpanDummy
172
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
173
+ end
174
+ end
175
+ end
176
+
177
+ return values.values.inject(0, &:+)
178
+ end
59
179
  end
180
+
60
181
  end
61
182
  end