prawn 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/Gemfile +18 -0
  2. data/README.md +5 -3
  3. data/Rakefile +8 -14
  4. data/data/pdfs/nested_pages.pdf +13 -13
  5. data/lib/prawn.rb +3 -1
  6. data/lib/prawn/compatibility.rb +46 -10
  7. data/lib/prawn/core.rb +3 -1
  8. data/lib/prawn/core/document_state.rb +2 -1
  9. data/lib/prawn/core/object_store.rb +61 -5
  10. data/lib/prawn/core/page.rb +3 -6
  11. data/lib/prawn/core/pdf_object.rb +21 -4
  12. data/lib/prawn/core/reference.rb +6 -2
  13. data/lib/prawn/core/text.rb +4 -4
  14. data/lib/prawn/core/text/formatted/line_wrap.rb +23 -8
  15. data/lib/prawn/document.rb +21 -15
  16. data/lib/prawn/document/bounding_box.rb +3 -3
  17. data/lib/prawn/document/column_box.rb +22 -4
  18. data/lib/prawn/document/snapshot.rb +1 -1
  19. data/lib/prawn/encoding.rb +1 -1
  20. data/lib/prawn/errors.rb +4 -0
  21. data/lib/prawn/font.rb +1 -1
  22. data/lib/prawn/font/afm.rb +30 -72
  23. data/lib/prawn/font/ttf.rb +6 -33
  24. data/lib/prawn/graphics.rb +148 -23
  25. data/lib/prawn/graphics/color.rb +8 -1
  26. data/lib/prawn/graphics/patterns.rb +137 -0
  27. data/lib/prawn/images.rb +25 -19
  28. data/lib/prawn/images/jpg.rb +4 -4
  29. data/lib/prawn/images/png.rb +18 -12
  30. data/lib/prawn/security.rb +6 -4
  31. data/lib/prawn/soft_mask.rb +94 -0
  32. data/lib/prawn/table.rb +136 -31
  33. data/lib/prawn/table/cell.rb +260 -29
  34. data/lib/prawn/table/cell/span_dummy.rb +88 -0
  35. data/lib/prawn/table/cell/text.rb +36 -14
  36. data/lib/prawn/table/cells.rb +91 -41
  37. data/lib/prawn/text.rb +3 -2
  38. data/lib/prawn/text/formatted/box.rb +14 -5
  39. data/lib/prawn/text/formatted/fragment.rb +33 -22
  40. data/lib/prawn/text/formatted/parser.rb +5 -2
  41. data/lib/prawn/utilities.rb +44 -0
  42. data/manual/basic_concepts/adding_pages.rb +27 -0
  43. data/manual/basic_concepts/basic_concepts.rb +34 -0
  44. data/manual/basic_concepts/creation.rb +39 -0
  45. data/manual/basic_concepts/cursor.rb +33 -0
  46. data/manual/basic_concepts/measurement.rb +25 -0
  47. data/manual/basic_concepts/origin.rb +38 -0
  48. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  49. data/manual/bounding_box/bounding_box.rb +39 -0
  50. data/manual/bounding_box/bounds.rb +49 -0
  51. data/manual/bounding_box/canvas.rb +24 -0
  52. data/manual/bounding_box/creation.rb +23 -0
  53. data/manual/bounding_box/indentation.rb +46 -0
  54. data/manual/bounding_box/nesting.rb +45 -0
  55. data/manual/bounding_box/russian_boxes.rb +40 -0
  56. data/manual/bounding_box/stretchy.rb +31 -0
  57. data/manual/document_and_page_options/background.rb +27 -0
  58. data/manual/document_and_page_options/document_and_page_options.rb +31 -0
  59. data/manual/document_and_page_options/metadata.rb +23 -0
  60. data/manual/document_and_page_options/page_margins.rb +38 -0
  61. data/manual/document_and_page_options/page_size.rb +34 -0
  62. data/manual/example_file.rb +116 -0
  63. data/manual/example_helper.rb +430 -0
  64. data/manual/example_package.rb +53 -0
  65. data/manual/example_section.rb +46 -0
  66. data/manual/graphics/circle_and_ellipse.rb +22 -0
  67. data/manual/graphics/color.rb +24 -0
  68. data/manual/graphics/common_lines.rb +28 -0
  69. data/manual/graphics/fill_and_stroke.rb +42 -0
  70. data/manual/graphics/fill_rules.rb +37 -0
  71. data/manual/graphics/gradients.rb +37 -0
  72. data/manual/graphics/graphics.rb +58 -0
  73. data/manual/graphics/helper.rb +17 -0
  74. data/manual/graphics/line_width.rb +35 -0
  75. data/manual/graphics/lines_and_curves.rb +41 -0
  76. data/manual/graphics/polygon.rb +29 -0
  77. data/manual/graphics/rectangle.rb +21 -0
  78. data/manual/graphics/rotate.rb +28 -0
  79. data/manual/graphics/scale.rb +41 -0
  80. data/manual/graphics/soft_masks.rb +46 -0
  81. data/manual/graphics/stroke_cap.rb +31 -0
  82. data/manual/graphics/stroke_dash.rb +43 -0
  83. data/manual/graphics/stroke_join.rb +30 -0
  84. data/manual/graphics/translate.rb +29 -0
  85. data/manual/graphics/transparency.rb +35 -0
  86. data/manual/images/absolute_position.rb +23 -0
  87. data/manual/images/fit.rb +21 -0
  88. data/manual/images/horizontal.rb +25 -0
  89. data/manual/images/images.rb +40 -0
  90. data/manual/images/plain_image.rb +18 -0
  91. data/manual/images/scale.rb +22 -0
  92. data/manual/images/vertical.rb +28 -0
  93. data/manual/images/width_and_height.rb +25 -0
  94. data/manual/layout/boxes.rb +27 -0
  95. data/manual/layout/content.rb +25 -0
  96. data/manual/layout/layout.rb +28 -0
  97. data/manual/layout/simple_grid.rb +23 -0
  98. data/manual/manual/cover.rb +26 -0
  99. data/manual/manual/foreword.rb +13 -0
  100. data/manual/manual/how_to_read_this_manual.rb +41 -0
  101. data/manual/manual/manual.rb +36 -0
  102. data/manual/outline/add_subsection_to.rb +61 -0
  103. data/manual/outline/insert_section_after.rb +47 -0
  104. data/manual/outline/outline.rb +32 -0
  105. data/manual/outline/sections_and_pages.rb +67 -0
  106. data/manual/repeatable_content/page_numbering.rb +54 -0
  107. data/manual/repeatable_content/repeatable_content.rb +31 -0
  108. data/manual/repeatable_content/repeater.rb +55 -0
  109. data/manual/repeatable_content/stamp.rb +41 -0
  110. data/manual/security/encryption.rb +31 -0
  111. data/manual/security/permissions.rb +38 -0
  112. data/manual/security/security.rb +28 -0
  113. data/manual/syntax_highlight.rb +52 -0
  114. data/manual/table/basic_block.rb +53 -0
  115. data/manual/table/before_rendering_page.rb +26 -0
  116. data/manual/table/cell_border_lines.rb +24 -0
  117. data/manual/table/cell_borders_and_bg.rb +31 -0
  118. data/manual/table/cell_dimensions.rb +30 -0
  119. data/manual/table/cell_text.rb +38 -0
  120. data/manual/table/column_widths.rb +30 -0
  121. data/manual/table/content_and_subtables.rb +39 -0
  122. data/manual/table/creation.rb +27 -0
  123. data/manual/table/filtering.rb +36 -0
  124. data/manual/table/flow_and_header.rb +17 -0
  125. data/manual/table/image_cells.rb +33 -0
  126. data/manual/table/position.rb +29 -0
  127. data/manual/table/row_colors.rb +20 -0
  128. data/manual/table/span.rb +30 -0
  129. data/manual/table/style.rb +22 -0
  130. data/manual/table/table.rb +52 -0
  131. data/manual/table/width.rb +27 -0
  132. data/manual/templates/full_template.rb +23 -0
  133. data/manual/templates/page_template.rb +47 -0
  134. data/manual/templates/templates.rb +26 -0
  135. data/manual/text/alignment.rb +44 -0
  136. data/manual/text/color.rb +24 -0
  137. data/manual/text/column_box.rb +32 -0
  138. data/manual/text/fallback_fonts.rb +37 -0
  139. data/manual/text/font.rb +41 -0
  140. data/manual/text/font_size.rb +45 -0
  141. data/manual/text/font_style.rb +23 -0
  142. data/manual/text/formatted_callbacks.rb +60 -0
  143. data/manual/text/formatted_text.rb +50 -0
  144. data/manual/text/free_flowing_text.rb +51 -0
  145. data/manual/text/group.rb +29 -0
  146. data/manual/text/inline.rb +43 -0
  147. data/manual/text/kerning_and_character_spacing.rb +39 -0
  148. data/manual/text/leading.rb +25 -0
  149. data/manual/text/line_wrapping.rb +41 -0
  150. data/manual/text/paragraph_indentation.rb +26 -0
  151. data/manual/text/positioned_text.rb +38 -0
  152. data/manual/text/registering_families.rb +48 -0
  153. data/manual/text/rendering_and_color.rb +37 -0
  154. data/manual/text/right_to_left_text.rb +43 -0
  155. data/manual/text/rotation.rb +43 -0
  156. data/manual/text/single_usage.rb +37 -0
  157. data/manual/text/text.rb +75 -0
  158. data/manual/text/text_box_excess.rb +32 -0
  159. data/manual/text/text_box_extensions.rb +45 -0
  160. data/manual/text/text_box_overflow.rb +44 -0
  161. data/manual/text/utf8.rb +28 -0
  162. data/manual/text/win_ansi_charset.rb +59 -0
  163. data/prawn.gemspec +10 -7
  164. data/spec/bounding_box_spec.rb +107 -17
  165. data/spec/cell_spec.rb +66 -40
  166. data/spec/column_box_spec.rb +33 -0
  167. data/spec/document_spec.rb +45 -24
  168. data/spec/extensions/encoding_helpers.rb +6 -0
  169. data/spec/extensions/mocha.rb +1 -0
  170. data/spec/font_spec.rb +71 -53
  171. data/spec/formatted_text_arranger_spec.rb +19 -19
  172. data/spec/formatted_text_box_spec.rb +16 -16
  173. data/spec/formatted_text_fragment_spec.rb +6 -6
  174. data/spec/graphics_spec.rb +96 -31
  175. data/spec/grid_spec.rb +2 -2
  176. data/spec/images_spec.rb +18 -10
  177. data/spec/jpg_spec.rb +1 -1
  178. data/spec/line_wrap_spec.rb +14 -14
  179. data/spec/measurement_units_spec.rb +2 -2
  180. data/spec/name_tree_spec.rb +6 -6
  181. data/spec/object_store_spec.rb +17 -17
  182. data/spec/outline_spec.rb +35 -17
  183. data/spec/pdf_object_spec.rb +3 -1
  184. data/spec/png_spec.rb +22 -19
  185. data/spec/reference_spec.rb +24 -1
  186. data/spec/repeater_spec.rb +9 -9
  187. data/spec/security_spec.rb +3 -3
  188. data/spec/snapshot_spec.rb +3 -3
  189. data/spec/soft_mask_spec.rb +117 -0
  190. data/spec/span_spec.rb +4 -4
  191. data/spec/spec_helper.rb +12 -6
  192. data/spec/stamp_spec.rb +12 -12
  193. data/spec/stroke_styles_spec.rb +5 -5
  194. data/spec/table_spec.rb +458 -88
  195. data/spec/template_spec.rb +108 -54
  196. data/spec/text_at_spec.rb +17 -17
  197. data/spec/text_box_spec.rb +76 -45
  198. data/spec/text_rendering_mode_spec.rb +5 -5
  199. data/spec/text_spacing_spec.rb +4 -4
  200. data/spec/text_spec.rb +44 -40
  201. metadata +419 -250
  202. data/lib/prawn/graphics/gradient.rb +0 -84
  203. data/lib/prawn/security/arcfour.rb +0 -51
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+
3
+ # span_dummy.rb: Placeholder for non-master spanned cells.
4
+ #
5
+ # Copyright December 2011, Brad Ediger. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ module Prawn
9
+ class Table
10
+ class Cell
11
+
12
+ # A Cell object used to represent all but the topmost cell in a span
13
+ # group.
14
+ #
15
+ class SpanDummy < Cell
16
+ def initialize(pdf, master_cell)
17
+ super(pdf, [0, pdf.cursor])
18
+ @master_cell = master_cell
19
+ @padding = [0, 0, 0, 0]
20
+ end
21
+
22
+ # By default, a span dummy will never increase the height demand.
23
+ #
24
+ def natural_content_height
25
+ 0
26
+ end
27
+
28
+ # By default, a span dummy will never increase the width demand.
29
+ #
30
+ def natural_content_width
31
+ 0
32
+ end
33
+
34
+ def avg_spanned_min_width
35
+ @master_cell.avg_spanned_min_width
36
+ end
37
+
38
+ # Dummy cells have nothing to draw.
39
+ #
40
+ def draw_borders(pt)
41
+ end
42
+
43
+ # Dummy cells have nothing to draw.
44
+ #
45
+ def draw_bounded_content(pt)
46
+ end
47
+
48
+ def padding_right=(val)
49
+ @master_cell.padding_right = val if rightmost?
50
+ end
51
+
52
+ def padding_bottom=(val)
53
+ @master_cell.padding_bottom = val if bottommost?
54
+ end
55
+
56
+ def border_right_color=(val)
57
+ @master_cell.border_right_color = val if rightmost?
58
+ end
59
+
60
+ def border_bottom_color=(val)
61
+ @master_cell.border_bottom_color = val if bottommost?
62
+ end
63
+
64
+ def border_right_width=(val)
65
+ @master_cell.border_right_width = val if rightmost?
66
+ end
67
+
68
+ def border_bottom_width=(val)
69
+ @master_cell.border_bottom_width = val if bottommost?
70
+ end
71
+
72
+ private
73
+
74
+ # Are we on the right border of the span?
75
+ #
76
+ def rightmost?
77
+ @column == @master_cell.column + @master_cell.colspan - 1
78
+ end
79
+
80
+ # Are we on the bottom border of the span?
81
+ #
82
+ def bottommost?
83
+ @row == @master_cell.row + @master_cell.rowspan - 1
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -47,7 +47,7 @@ module Prawn
47
47
  # from the final width if the text is long.
48
48
  #
49
49
  def natural_content_width
50
- [styled_width_of(@content), @pdf.bounds.width].min
50
+ @natural_content_width ||= [styled_width_of(@content), @pdf.bounds.width].min
51
51
  end
52
52
 
53
53
  # Returns the natural height of this block of text, wrapped to the
@@ -55,7 +55,7 @@ module Prawn
55
55
  #
56
56
  def natural_content_height
57
57
  with_font do
58
- b = text_box(:width => content_width + FPTolerance)
58
+ b = text_box(:width => spanned_content_width + FPTolerance)
59
59
  b.render(:dry_run => true)
60
60
  b.height + b.line_gap
61
61
  end
@@ -67,8 +67,8 @@ module Prawn
67
67
  with_font do
68
68
  @pdf.move_down((@pdf.font.line_gap + @pdf.font.descender)/2)
69
69
  with_text_color do
70
- text_box(:width => content_width + FPTolerance,
71
- :height => content_height + FPTolerance,
70
+ text_box(:width => spanned_content_width + FPTolerance,
71
+ :height => spanned_content_height + FPTolerance,
72
72
  :at => [0, @pdf.cursor]).render
73
73
  end
74
74
  end
@@ -78,9 +78,11 @@ module Prawn
78
78
  # Sets a reasonable minimum width. If the cell has any content, make
79
79
  # sure we have enough width to be at least one character wide. This is
80
80
  # a bit of a hack, but it should work well enough.
81
- min_content_width = [natural_content_width, styled_width_of("M")].min
82
- @min_width ||= padding_left + padding_right + min_content_width
83
- super
81
+ unless @min_width
82
+ min_content_width = [natural_content_width, styled_width_of_single_character].min
83
+ @min_width = padding_left + padding_right + min_content_width
84
+ super
85
+ end
84
86
  end
85
87
 
86
88
  protected
@@ -97,21 +99,28 @@ module Prawn
97
99
  end
98
100
 
99
101
  def with_text_color
100
- old_color = @pdf.fill_color || '000000'
101
- @pdf.fill_color(@text_color) if @text_color
102
- yield
103
- ensure
104
- @pdf.fill_color(old_color)
102
+ if @text_color
103
+ begin
104
+ old_color = @pdf.fill_color || '000000'
105
+ @pdf.fill_color(@text_color)
106
+ yield
107
+ ensure
108
+ @pdf.fill_color(old_color)
109
+ end
110
+ else
111
+ yield
112
+ end
105
113
  end
106
114
 
107
115
  def text_box(extra_options={})
108
116
  if @text_options[:inline_format]
109
117
  options = @text_options.dup
110
118
  options.delete(:inline_format)
119
+ options.merge!(extra_options)
120
+ options[:document] = @pdf
111
121
 
112
122
  array = ::Prawn::Text::Formatted::Parser.to_array(@content)
113
- ::Prawn::Text::Formatted::Box.new(array,
114
- options.merge(extra_options).merge(:document => @pdf))
123
+ ::Prawn::Text::Formatted::Box.new(array, options)
115
124
  else
116
125
  ::Prawn::Text::Box.new(@content, @text_options.merge(extra_options).
117
126
  merge(:document => @pdf))
@@ -124,6 +133,19 @@ module Prawn
124
133
  @pdf.width_of(text, @text_options)
125
134
  end
126
135
 
136
+ private
137
+
138
+ # Returns the greatest possible width of any single character
139
+ # under the given text options.
140
+ # (We use this to determine the minimum width of a table cell)
141
+ # (Although we currently determine this by measuring "M", it should really
142
+ # use whichever character is widest under the current font)
143
+ #
144
+ def styled_width_of_single_character
145
+ key = (@text_options[:style] == :bold) ? :bold_char_width : :plain_char_width
146
+ cache = Thread.current[key] ||= {}
147
+ cache[@pdf.font] ||= styled_width_of("M")
148
+ end
127
149
  end
128
150
  end
129
151
  end
@@ -9,13 +9,6 @@
9
9
  module Prawn
10
10
  class Table
11
11
 
12
- # Returns a Cells object that can be used to select and style cells. See
13
- # the Cells documentation for things you can do with cells.
14
- #
15
- def cells
16
- @cell_proxy ||= Cells.new(@cells)
17
- end
18
-
19
12
  # Selects the given rows (0-based) for styling. Returns a Cells object --
20
13
  # see the documentation on Cells for things you can do with cells.
21
14
  #
@@ -54,13 +47,20 @@ module Prawn
54
47
  #
55
48
  def rows(row_spec)
56
49
  index_cells unless @indexed
57
- row_spec = transform_spec(row_spec, @row_count)
50
+ row_spec = transform_spec(row_spec, @first_row, @row_count)
58
51
  Cells.new(@rows[row_spec] ||= select { |c|
59
52
  row_spec.respond_to?(:include?) ?
60
53
  row_spec.include?(c.row) : row_spec === c.row })
61
54
  end
62
55
  alias_method :row, :rows
63
-
56
+
57
+ # Returns the number of rows in the list.
58
+ #
59
+ def row_count
60
+ index_cells unless @indexed
61
+ @row_count
62
+ end
63
+
64
64
  # Limits selection to the given column or columns. +col_spec+ can be
65
65
  # anything that responds to the === operator selecting a set of 0-based
66
66
  # column numbers; most commonly a number or a range.
@@ -70,13 +70,20 @@ module Prawn
70
70
  #
71
71
  def columns(col_spec)
72
72
  index_cells unless @indexed
73
- col_spec = transform_spec(col_spec, @column_count)
73
+ col_spec = transform_spec(col_spec, @first_column, @column_count)
74
74
  Cells.new(@columns[col_spec] ||= select { |c|
75
75
  col_spec.respond_to?(:include?) ?
76
76
  col_spec.include?(c.column) : col_spec === c.column })
77
77
  end
78
78
  alias_method :column, :columns
79
79
 
80
+ # Returns the number of columns in the list.
81
+ #
82
+ def column_count
83
+ index_cells unless @indexed
84
+ @column_count
85
+ end
86
+
80
87
  # Allows you to filter the given cells by arbitrary properties.
81
88
  #
82
89
  # table.column(4).filter { |cell| cell.content =~ /Yes/ }.
@@ -92,7 +99,33 @@ module Prawn
92
99
  # table.cells[0, 0].content # => "First cell content"
93
100
  #
94
101
  def [](row, col)
95
- find { |c| c.row == row && c.column == col }
102
+ return nil if empty?
103
+ index_cells unless @indexed
104
+ row_array, col_array = @rows[@first_row + row] || [], @columns[@first_column + col] || []
105
+ if row_array.length < col_array.length
106
+ row_array.find { |c| c.column == @first_column + col }
107
+ else
108
+ col_array.find { |c| c.row == @first_row + row }
109
+ end
110
+ end
111
+
112
+ # Puts a cell in the collection at the given position. Internal use only.
113
+ #
114
+ def []=(row, col, cell) # :nodoc:
115
+ cell.extend(Cell::InTable)
116
+ cell.row = row
117
+ cell.column = col
118
+
119
+ if @indexed
120
+ (@rows[row] ||= []) << cell
121
+ (@columns[col] ||= []) << cell
122
+ @first_row = row if !@first_row || row < @first_row
123
+ @first_column = col if !@first_column || col < @first_column
124
+ @row_count = @rows.size
125
+ @column_count = @columns.size
126
+ end
127
+
128
+ self << cell
96
129
  end
97
130
 
98
131
  # Supports setting multiple properties at once.
@@ -110,51 +143,43 @@ module Prawn
110
143
  # table.cells.style { |cell| cell.border_width += 12 }
111
144
  #
112
145
  def style(options={}, &block)
113
- each { |cell| cell.style(options, &block) }
146
+ each do |cell|
147
+ next if cell.is_a?(Cell::SpanDummy)
148
+ cell.style(options, &block)
149
+ end
114
150
  end
115
151
 
116
152
  # Returns the total width of all columns in the selected set.
117
153
  #
118
154
  def width
119
- column_widths = {}
120
- each do |cell|
121
- column_widths[cell.column] =
122
- [column_widths[cell.column], cell.width].compact.max
155
+ widths = {}
156
+ each do |cell|
157
+ index = cell.column
158
+ per_cell_width = cell.width_ignoring_span.to_f / cell.colspan
159
+ cell.colspan.times do |n|
160
+ widths[cell.column+n] = [widths[cell.column+n], per_cell_width].
161
+ compact.max
162
+ end
123
163
  end
124
- column_widths.values.inject(0) { |sum, width| sum + width }
164
+ widths.values.inject(0, &:+)
125
165
  end
126
166
 
127
167
  # Returns minimum width required to contain cells in the set.
128
168
  #
129
169
  def min_width
130
- column_min_widths = {}
131
- each do |cell|
132
- column_min_widths[cell.column] =
133
- [column_min_widths[cell.column], cell.min_width].compact.max
134
- end
135
- column_min_widths.values.inject(0) { |sum, width| sum + width }
170
+ aggregate_cell_values(:column, :avg_spanned_min_width, :max)
136
171
  end
137
172
 
138
173
  # Returns maximum width that can contain cells in the set.
139
174
  #
140
175
  def max_width
141
- column_max_widths = {}
142
- each do |cell|
143
- column_max_widths[cell.column] =
144
- [column_max_widths[cell.column], cell.max_width].compact.min
145
- end
146
- column_max_widths.values.inject(0) { |sum, width| sum + width }
176
+ aggregate_cell_values(:column, :max_width_ignoring_span, :min)
147
177
  end
148
178
 
149
179
  # Returns the total height of all rows in the selected set.
150
180
  #
151
181
  def height
152
- row_heights = {}
153
- each do |cell|
154
- row_heights[cell.row] =
155
- [row_heights[cell.row], cell.height].compact.max
156
- end
157
- row_heights.values.inject(0) { |sum, width| sum + width }
182
+ aggregate_cell_values(:row, :height_ignoring_span, :max)
158
183
  end
159
184
 
160
185
  # Supports setting arbitrary properties on a group of cells.
@@ -162,7 +187,11 @@ module Prawn
162
187
  # table.cells.row(3..6).background_color = 'cc0000'
163
188
  #
164
189
  def method_missing(id, *args, &block)
165
- each { |c| c.send(id, *args, &block) }
190
+ if id.to_s =~ /=\z/
191
+ each { |c| c.send(id, *args, &block) if c.respond_to?(id) }
192
+ else
193
+ super
194
+ end
166
195
  end
167
196
 
168
197
  protected
@@ -185,24 +214,45 @@ module Prawn
185
214
  @columns[cell.column] << cell
186
215
  end
187
216
 
217
+ @first_row = @rows.keys.min
218
+ @first_column = @columns.keys.min
219
+
188
220
  @row_count = @rows.size
189
221
  @column_count = @columns.size
190
222
 
191
223
  @indexed = true
192
224
  end
193
225
 
226
+ # Sum up a min/max value over rows or columns in the cells selected.
227
+ # Takes the min/max (per +aggregate+) of the result of sending +meth+ to
228
+ # each cell, grouped by +row_or_column+.
229
+ #
230
+ def aggregate_cell_values(row_or_column, meth, aggregate)
231
+ values = {}
232
+ each do |cell|
233
+ index = cell.send(row_or_column)
234
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
235
+ end
236
+ values.values.inject(0, &:+)
237
+ end
238
+
194
239
  # Transforms +spec+, a column / row specification, into an object that
195
240
  # can be compared against a row or column number using ===. Normalizes
196
- # negative indices to be positive, given a total size of +total+.
241
+ # negative indices to be positive, given a total size of +total+. The
242
+ # first row/column is indicated by +first+; this value is considered row
243
+ # or column 0.
197
244
  #
198
- def transform_spec(spec, total)
245
+ def transform_spec(spec, first, total)
199
246
  case spec
200
247
  when Range
201
- transform_spec(spec.begin, total)..transform_spec(spec.end, total)
248
+ transform_spec(spec.begin, first, total) ..
249
+ transform_spec(spec.end, first, total)
202
250
  when Integer
203
- spec < 0 ? (total + spec) : spec
251
+ spec < 0 ? (first + total + spec) : first + spec
252
+ when Enumerable
253
+ spec.map { |x| first + x }
204
254
  else # pass through
205
- spec
255
+ raise "Don't understand spec #{spec.inspect}"
206
256
  end
207
257
  end
208
258
  end
@@ -152,6 +152,7 @@ module Prawn
152
152
  # any text
153
153
  #
154
154
  def text(string, options={})
155
+ return false if string.nil?
155
156
  # we modify the options. don't change the user's hash
156
157
  options = options.dup
157
158
 
@@ -176,7 +177,7 @@ module Prawn
176
177
  # text([{ :text => "hello" },
177
178
  # { :text => "world",
178
179
  # :size => 24,
179
- # :style => [:bold, :italic] }])
180
+ # :styles => [:bold, :italic] }])
180
181
  #
181
182
  # == Options
182
183
  #
@@ -314,7 +315,7 @@ module Prawn
314
315
  # height_of_formatted([{ :text => "hello" },
315
316
  # { :text => "world",
316
317
  # :size => 24,
317
- # :style => [:bold, :italic] }])
318
+ # :styles => [:bold, :italic] }])
318
319
  #
319
320
  def height_of_formatted(array, options={})
320
321
  if options[:indent_paragraphs]
@@ -49,6 +49,9 @@ module Prawn
49
49
  # created to that destination. Note that you must explicitly underline
50
50
  # and color using the appropriate tags if you which to draw attention
51
51
  # to the link
52
+ # <tt>:draw_text_callback</tt>:
53
+ # if provided, this Proc will be called instead of #draw_text! once
54
+ # per fragment for every low-level addition of text to the page.
52
55
  # <tt>:callback</tt>::
53
56
  # an object (or array of such objects) with two methods:
54
57
  # #render_behind and #render_in_front, which are called immediately
@@ -101,7 +104,8 @@ module Prawn
101
104
  :skip_encoding,
102
105
  :document,
103
106
  :direction,
104
- :fallback_fonts]
107
+ :fallback_fonts,
108
+ :draw_text_callback]
105
109
  end
106
110
 
107
111
  # The text that was successfully printed (or, if <tt>dry_run</tt> was
@@ -205,6 +209,7 @@ module Prawn
205
209
  @rotate_around = options[:rotate_around] || :upper_left
206
210
  @single_line = options[:single_line]
207
211
  @skip_encoding = options[:skip_encoding] || @document.skip_encoding
212
+ @draw_text_callback = options[:draw_text_callback]
208
213
 
209
214
  if @overflow == :expand
210
215
  # if set to expand, then we simply set the bottom
@@ -310,8 +315,13 @@ module Prawn
310
315
  draw_fragment_underlays(fragment)
311
316
 
312
317
  @document.word_spacing(word_spacing) {
313
- @document.draw_text!(fragment.text, :at => [x, y],
314
- :kerning => @kerning)
318
+ if @draw_text_callback
319
+ @draw_text_callback.call(fragment.text, :at => [x, y],
320
+ :kerning => @kerning)
321
+ else
322
+ @document.draw_text!(fragment.text, :at => [x, y],
323
+ :kerning => @kerning)
324
+ end
315
325
  }
316
326
 
317
327
  draw_fragment_overlays(fragment)
@@ -371,8 +381,7 @@ module Prawn
371
381
  # all fonts
372
382
  fallback_fonts << fragment_font
373
383
 
374
- hash[:text].unpack("U*").each do |char_int|
375
- char = [char_int].pack("U")
384
+ hash[:text].unicode_characters do |char|
376
385
  @document.font(fragment_font)
377
386
  font_glyph_pairs << [find_font_for_this_glyph(char,
378
387
  @document.font.family,