prawn 1.0.0.rc1 → 1.0.0.rc2

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 (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,