prawn 0.11.1.pre → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. data/COPYING +2 -340
  2. data/HACKING +1 -1
  3. data/LICENSE +3 -3
  4. data/Rakefile +17 -6
  5. data/data/encodings/win_ansi.txt +1 -1
  6. data/data/images/prawn.png +0 -0
  7. data/data/pdfs/form.pdf +820 -0
  8. data/data/pdfs/multipage_template.pdf +127 -0
  9. data/examples/bounding_box/bounding_boxes.rb +4 -3
  10. data/examples/bounding_box/indentation.rb +2 -1
  11. data/examples/bounding_box/russian_boxes.rb +3 -2
  12. data/examples/bounding_box/stretched_nesting.rb +2 -1
  13. data/examples/general/background.rb +2 -1
  14. data/examples/general/canvas.rb +2 -1
  15. data/examples/general/context_sensitive_headers.rb +2 -1
  16. data/examples/general/float.rb +2 -1
  17. data/examples/general/margin.rb +2 -1
  18. data/examples/general/measurement_units.rb +2 -1
  19. data/examples/general/metadata-info.rb +2 -1
  20. data/examples/general/multi_page_layout.rb +2 -1
  21. data/examples/general/outlines.rb +2 -1
  22. data/examples/general/page_geometry.rb +2 -1
  23. data/examples/general/page_numbering.rb +27 -2
  24. data/examples/general/page_templates.rb +20 -0
  25. data/examples/general/repeaters.rb +2 -1
  26. data/examples/general/stamp.rb +4 -3
  27. data/examples/general/templates.rb +2 -1
  28. data/examples/graphics/basic_images.rb +2 -1
  29. data/examples/graphics/cmyk.rb +2 -1
  30. data/examples/graphics/curves.rb +4 -3
  31. data/examples/graphics/gradient.rb +23 -0
  32. data/examples/graphics/hexagon.rb +3 -2
  33. data/examples/graphics/image_fit.rb +3 -2
  34. data/examples/graphics/image_flow.rb +2 -1
  35. data/examples/graphics/image_position.rb +3 -2
  36. data/examples/graphics/line.rb +2 -1
  37. data/examples/graphics/png_types.rb +3 -2
  38. data/examples/graphics/polygons.rb +3 -2
  39. data/examples/graphics/remote_images.rb +2 -1
  40. data/examples/graphics/rounded_polygons.rb +2 -1
  41. data/examples/graphics/rounded_rectangle.rb +2 -1
  42. data/examples/graphics/ruport_style_helpers.rb +3 -2
  43. data/examples/graphics/stroke_bounds.rb +2 -1
  44. data/examples/graphics/stroke_cap_and_join.rb +2 -1
  45. data/examples/graphics/stroke_dash.rb +2 -1
  46. data/examples/graphics/transformations.rb +2 -1
  47. data/examples/graphics/transparency.rb +4 -3
  48. data/examples/grid/bounding_boxes.rb +2 -1
  49. data/examples/grid/column_gutter_grid.rb +2 -1
  50. data/examples/grid/multi_boxes.rb +2 -1
  51. data/examples/grid/show_grid.rb +2 -1
  52. data/examples/grid/simple_grid.rb +2 -1
  53. data/examples/m17n/chinese_text_wrapping.rb +2 -1
  54. data/examples/m17n/euro.rb +3 -2
  55. data/examples/m17n/full_win_ansi_character_list.rb +20 -0
  56. data/examples/m17n/sjis.rb +2 -1
  57. data/examples/m17n/utf8.rb +3 -2
  58. data/examples/m17n/win_ansi_charset.rb +2 -1
  59. data/examples/security/hello_foo.rb +2 -1
  60. data/examples/table/bill.rb +2 -1
  61. data/examples/table/borders.rb +25 -0
  62. data/examples/table/cell.rb +3 -2
  63. data/examples/table/checkerboard.rb +2 -1
  64. data/examples/table/header.rb +3 -2
  65. data/examples/table/inline_format_table.rb +2 -1
  66. data/examples/table/multi_page_table.rb +2 -1
  67. data/examples/table/simple_table.rb +2 -1
  68. data/examples/table/subtable.rb +2 -1
  69. data/examples/table/widths.rb +2 -1
  70. data/examples/text/alignment.rb +2 -1
  71. data/examples/text/character_spacing.rb +2 -1
  72. data/examples/text/dfont.rb +2 -1
  73. data/examples/text/family_based_styling.rb +3 -2
  74. data/examples/text/font_calculations.rb +2 -1
  75. data/examples/text/font_size.rb +2 -1
  76. data/examples/text/hyphenation.rb +2 -2
  77. data/examples/text/indent_paragraphs.rb +7 -5
  78. data/examples/text/inline_format.rb +7 -6
  79. data/examples/text/kerning.rb +2 -1
  80. data/examples/text/rendering_mode.rb +21 -0
  81. data/examples/text/rotated.rb +2 -1
  82. data/examples/text/shaped_text_box.rb +2 -1
  83. data/examples/text/simple_text.rb +2 -1
  84. data/examples/text/simple_text_ttf.rb +2 -1
  85. data/examples/text/span.rb +3 -2
  86. data/examples/text/text_box.rb +7 -5
  87. data/examples/text/text_box_returning_excess.rb +4 -3
  88. data/examples/text/text_flow.rb +2 -1
  89. data/lib/prawn.rb +1 -1
  90. data/lib/prawn/core/object_store.rb +42 -14
  91. data/lib/prawn/core/page.rb +22 -8
  92. data/lib/prawn/core/text.rb +141 -13
  93. data/lib/prawn/core/text/formatted/arranger.rb +39 -12
  94. data/lib/prawn/core/text/formatted/line_wrap.rb +205 -60
  95. data/lib/prawn/core/text/formatted/wrap.rb +72 -35
  96. data/lib/prawn/document.rb +174 -70
  97. data/lib/prawn/document/bounding_box.rb +122 -83
  98. data/lib/prawn/document/column_box.rb +113 -0
  99. data/lib/prawn/document/graphics_state.rb +90 -2
  100. data/lib/prawn/document/internals.rb +5 -3
  101. data/lib/prawn/errors.rb +5 -0
  102. data/lib/prawn/font.rb +4 -4
  103. data/lib/prawn/font/afm.rb +11 -0
  104. data/lib/prawn/font/ttf.rb +5 -0
  105. data/lib/prawn/graphics.rb +77 -14
  106. data/lib/prawn/graphics/cap_style.rb +13 -5
  107. data/lib/prawn/graphics/color.rb +54 -35
  108. data/lib/prawn/graphics/dash.rb +27 -16
  109. data/lib/prawn/graphics/gradient.rb +84 -0
  110. data/lib/prawn/graphics/join_style.rb +12 -3
  111. data/lib/prawn/graphics/transparency.rb +4 -4
  112. data/lib/prawn/images.rb +18 -160
  113. data/lib/prawn/images/jpg.rb +39 -0
  114. data/lib/prawn/images/png.rb +130 -0
  115. data/lib/prawn/repeater.rb +6 -13
  116. data/lib/prawn/security.rb +6 -1
  117. data/lib/prawn/stamp.rb +12 -4
  118. data/lib/prawn/table.rb +36 -4
  119. data/lib/prawn/table/cell.rb +224 -63
  120. data/lib/prawn/table/cell/text.rb +20 -10
  121. data/lib/prawn/table/cells.rb +23 -6
  122. data/lib/prawn/text.rb +54 -91
  123. data/lib/prawn/text/box.rb +29 -283
  124. data/lib/prawn/text/formatted/box.rb +349 -24
  125. data/lib/prawn/text/formatted/fragment.rb +63 -2
  126. data/lib/prawn/text/formatted/parser.rb +2 -1
  127. data/prawn.gemspec +21 -5
  128. data/spec/bounding_box_spec.rb +61 -28
  129. data/spec/cell_spec.rb +168 -30
  130. data/spec/document_spec.rb +187 -3
  131. data/spec/extensions/mocha.rb +45 -0
  132. data/spec/font_spec.rb +32 -1
  133. data/spec/formatted_text_arranger_spec.rb +35 -40
  134. data/spec/formatted_text_box_spec.rb +287 -443
  135. data/spec/formatted_text_fragment_spec.rb +87 -0
  136. data/spec/graphics_spec.rb +128 -12
  137. data/spec/grid_spec.rb +1 -1
  138. data/spec/images_spec.rb +11 -3
  139. data/spec/inline_formatted_text_parser_spec.rb +8 -0
  140. data/spec/line_wrap_spec.rb +200 -208
  141. data/spec/object_store_spec.rb +10 -0
  142. data/spec/outline_spec.rb +7 -3
  143. data/spec/repeater_spec.rb +58 -10
  144. data/spec/security_spec.rb +6 -0
  145. data/spec/spec_helper.rb +12 -8
  146. data/spec/stamp_spec.rb +52 -1
  147. data/spec/stroke_styles_spec.rb +30 -0
  148. data/spec/table_spec.rb +93 -3
  149. data/spec/template_spec.rb +132 -6
  150. data/spec/text_at_spec.rb +14 -4
  151. data/spec/text_box_spec.rb +309 -70
  152. data/spec/text_rendering_mode_spec.rb +45 -0
  153. data/spec/text_spec.rb +60 -17
  154. data/spec/text_with_inline_formatting_spec.rb +4 -162
  155. metadata +241 -241
  156. data/lib/prawn/core/text/line_wrap.rb +0 -211
  157. data/lib/prawn/core/text/wrap.rb +0 -82
  158. data/vendor/pdf-inspector/README +0 -18
  159. data/vendor/pdf-inspector/lib/pdf/inspector.rb +0 -26
  160. data/vendor/pdf-inspector/lib/pdf/inspector/extgstate.rb +0 -18
  161. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +0 -131
  162. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +0 -25
  163. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +0 -46
  164. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +0 -19
  165. data/vendor/ttfunk/data/fonts/DejaVuSans.ttf +0 -0
  166. data/vendor/ttfunk/data/fonts/comicsans.ttf +0 -0
  167. data/vendor/ttfunk/example.rb +0 -45
  168. data/vendor/ttfunk/lib/ttfunk.rb +0 -102
  169. data/vendor/ttfunk/lib/ttfunk/directory.rb +0 -17
  170. data/vendor/ttfunk/lib/ttfunk/encoding/mac_roman.rb +0 -88
  171. data/vendor/ttfunk/lib/ttfunk/encoding/windows_1252.rb +0 -69
  172. data/vendor/ttfunk/lib/ttfunk/reader.rb +0 -44
  173. data/vendor/ttfunk/lib/ttfunk/resource_file.rb +0 -78
  174. data/vendor/ttfunk/lib/ttfunk/subset.rb +0 -18
  175. data/vendor/ttfunk/lib/ttfunk/subset/base.rb +0 -141
  176. data/vendor/ttfunk/lib/ttfunk/subset/mac_roman.rb +0 -50
  177. data/vendor/ttfunk/lib/ttfunk/subset/unicode.rb +0 -48
  178. data/vendor/ttfunk/lib/ttfunk/subset/unicode_8bit.rb +0 -63
  179. data/vendor/ttfunk/lib/ttfunk/subset/windows_1252.rb +0 -55
  180. data/vendor/ttfunk/lib/ttfunk/subset_collection.rb +0 -72
  181. data/vendor/ttfunk/lib/ttfunk/table.rb +0 -46
  182. data/vendor/ttfunk/lib/ttfunk/table/cmap.rb +0 -34
  183. data/vendor/ttfunk/lib/ttfunk/table/cmap/format00.rb +0 -54
  184. data/vendor/ttfunk/lib/ttfunk/table/cmap/format04.rb +0 -126
  185. data/vendor/ttfunk/lib/ttfunk/table/cmap/subtable.rb +0 -79
  186. data/vendor/ttfunk/lib/ttfunk/table/glyf.rb +0 -64
  187. data/vendor/ttfunk/lib/ttfunk/table/glyf/compound.rb +0 -81
  188. data/vendor/ttfunk/lib/ttfunk/table/glyf/simple.rb +0 -37
  189. data/vendor/ttfunk/lib/ttfunk/table/head.rb +0 -44
  190. data/vendor/ttfunk/lib/ttfunk/table/hhea.rb +0 -41
  191. data/vendor/ttfunk/lib/ttfunk/table/hmtx.rb +0 -47
  192. data/vendor/ttfunk/lib/ttfunk/table/kern.rb +0 -79
  193. data/vendor/ttfunk/lib/ttfunk/table/kern/format0.rb +0 -62
  194. data/vendor/ttfunk/lib/ttfunk/table/loca.rb +0 -43
  195. data/vendor/ttfunk/lib/ttfunk/table/maxp.rb +0 -40
  196. data/vendor/ttfunk/lib/ttfunk/table/name.rb +0 -125
  197. data/vendor/ttfunk/lib/ttfunk/table/os2.rb +0 -78
  198. data/vendor/ttfunk/lib/ttfunk/table/post.rb +0 -91
  199. data/vendor/ttfunk/lib/ttfunk/table/post/format10.rb +0 -43
  200. data/vendor/ttfunk/lib/ttfunk/table/post/format20.rb +0 -35
  201. data/vendor/ttfunk/lib/ttfunk/table/post/format25.rb +0 -23
  202. data/vendor/ttfunk/lib/ttfunk/table/post/format30.rb +0 -17
  203. data/vendor/ttfunk/lib/ttfunk/table/post/format40.rb +0 -17
  204. data/vendor/ttfunk/lib/ttfunk/table/simple.rb +0 -14
@@ -129,6 +129,136 @@ module Prawn
129
129
  8
130
130
  end
131
131
 
132
+ # Build a PDF object representing this image in +document+, and return
133
+ # a Reference to it.
134
+ #
135
+ def build_pdf_object(document)
136
+ if compression_method != 0
137
+ raise Errors::UnsupportedImageType,
138
+ 'PNG uses an unsupported compression method'
139
+ end
140
+
141
+ if filter_method != 0
142
+ raise Errors::UnsupportedImageType,
143
+ 'PNG uses an unsupported filter method'
144
+ end
145
+
146
+ if interlace_method != 0
147
+ raise Errors::UnsupportedImageType,
148
+ 'PNG uses unsupported interlace method'
149
+ end
150
+
151
+ # some PNG types store the colour and alpha channel data together,
152
+ # which the PDF spec doesn't like, so split it out.
153
+ split_alpha_channel!
154
+
155
+ case colors
156
+ when 1
157
+ color = :DeviceGray
158
+ when 3
159
+ color = :DeviceRGB
160
+ else
161
+ raise Errors::UnsupportedImageType,
162
+ "PNG uses an unsupported number of colors (#{png.colors})"
163
+ end
164
+
165
+ # build the image dict
166
+ obj = document.ref!(
167
+ :Type => :XObject,
168
+ :Subtype => :Image,
169
+ :Height => height,
170
+ :Width => width,
171
+ :BitsPerComponent => bits,
172
+ :Length => img_data.size,
173
+ :Filter => :FlateDecode
174
+ )
175
+
176
+ unless alpha_channel
177
+ obj.data[:DecodeParms] = {:Predictor => 15,
178
+ :Colors => colors,
179
+ :BitsPerComponent => bits,
180
+ :Columns => width}
181
+ end
182
+
183
+ # append the actual image data to the object as a stream
184
+ obj << img_data
185
+
186
+ # sort out the colours of the image
187
+ if palette.empty?
188
+ obj.data[:ColorSpace] = color
189
+ else
190
+ # embed the colour palette in the PDF as a object stream
191
+ palette_obj = document.ref!(:Length => palette.size)
192
+ palette_obj << palette
193
+
194
+ # build the color space array for the image
195
+ obj.data[:ColorSpace] = [:Indexed,
196
+ :DeviceRGB,
197
+ (palette.size / 3) -1,
198
+ palette_obj]
199
+ end
200
+
201
+ # *************************************
202
+ # add transparency data if necessary
203
+ # *************************************
204
+
205
+ # For PNG color types 0, 2 and 3, the transparency data is stored in
206
+ # a dedicated PNG chunk, and is exposed via the transparency attribute
207
+ # of the PNG class.
208
+ if transparency[:grayscale]
209
+ # Use Color Key Masking (spec section 4.8.5)
210
+ # - An array with N elements, where N is two times the number of color
211
+ # components.
212
+ val = transparency[:grayscale]
213
+ obj.data[:Mask] = [val, val]
214
+ elsif transparency[:rgb]
215
+ # Use Color Key Masking (spec section 4.8.5)
216
+ # - An array with N elements, where N is two times the number of color
217
+ # components.
218
+ rgb = transparency[:rgb]
219
+ obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten
220
+ elsif transparency[:indexed]
221
+ # TODO: broken. I was attempting to us Color Key Masking, but I think
222
+ # we need to construct an SMask i think. Maybe do it inside
223
+ # the PNG class, and store it in alpha_channel
224
+ #obj.data[:Mask] = transparency[:indexed]
225
+ end
226
+
227
+ # For PNG color types 4 and 6, the transparency data is stored as a alpha
228
+ # channel mixed in with the main image data. The PNG class seperates
229
+ # it out for us and makes it available via the alpha_channel attribute
230
+ if alpha_channel?
231
+ smask_obj = document.ref!(
232
+ :Type => :XObject,
233
+ :Subtype => :Image,
234
+ :Height => height,
235
+ :Width => width,
236
+ :BitsPerComponent => alpha_channel_bits,
237
+ :Length => alpha_channel.size,
238
+ :Filter => :FlateDecode,
239
+ :ColorSpace => :DeviceGray,
240
+ :Decode => [0, 1]
241
+ )
242
+ smask_obj << alpha_channel
243
+ obj.data[:SMask] = smask_obj
244
+ end
245
+
246
+ obj
247
+ end
248
+
249
+ # Returns the minimum PDF version required to support this image.
250
+ def min_pdf_version
251
+ if bits > 8
252
+ # 16-bit color only supported in 1.5+ (ISO 32000-1:2008 8.9.5.1)
253
+ 1.5
254
+ elsif alpha_channel?
255
+ # Need transparency for SMask
256
+ 1.4
257
+ else
258
+ 1.0
259
+ end
260
+ end
261
+
132
262
  private
133
263
 
134
264
  def unfilter_image_data
@@ -96,30 +96,23 @@ module Prawn
96
96
  @stamp_name = "prawn_repeater(#{Repeater.count})"
97
97
  @document.create_stamp(@stamp_name, &block) unless dynamic
98
98
  @block = block if dynamic
99
+ @graphic_state = document.state.page.graphic_state.dup
99
100
 
100
101
  Repeater.count += 1
101
102
  end
102
103
 
103
104
  def match?(page_number)
104
- case @page_filter
105
- when :all
106
- true
107
- when :odd
108
- page_number % 2 == 1
109
- when :even
110
- page_number % 2 == 0
111
- when Range, Array
112
- @page_filter.include?(page_number)
113
- when Proc
114
- @page_filter.call(page_number)
115
- end
105
+ @document.page_match?(@page_filter, page_number)
116
106
  end
117
107
 
118
108
  def run(page_number)
119
109
  if !@dynamic
120
110
  @document.stamp(@stamp_name) if match?(page_number)
121
111
  elsif @block
122
- @block.arity < 1 ? @document.instance_eval(&@block) : @block[@document]
112
+ @document.save_graphics_state(@graphic_state) do
113
+ @document.send(:freeze_stamp_graphics)
114
+ @block.call
115
+ end
123
116
  end
124
117
  end
125
118
 
@@ -47,7 +47,7 @@ module Prawn
47
47
  #
48
48
  # <tt>:print_document</tt>:: Print document.
49
49
  #
50
- # <tt>:modify_document</tt>:: Modify contents of document (other than text
50
+ # <tt>:modify_contents</tt>:: Modify contents of document (other than text
51
51
  # annotations and interactive form fields).
52
52
  #
53
53
  # <tt>:copy_contents</tt>:: Copy text and graphics from document.
@@ -142,6 +142,11 @@ module Prawn
142
142
  def permissions=(perms={})
143
143
  @permissions ||= FullPermissions
144
144
  perms.each do |key, value|
145
+ unless PermissionsBits[key]
146
+ raise ArgumentError, "Unknown permission :#{key}. Valid options: " +
147
+ PermissionsBits.keys.map { |k| k.inspect }.join(", ")
148
+ end
149
+
145
150
  # 0-based bit number, from LSB
146
151
  bit_position = PermissionsBits[key] - 1
147
152
 
data/lib/prawn/stamp.rb CHANGED
@@ -21,7 +21,7 @@ module Prawn
21
21
  #
22
22
  # Example:
23
23
  # pdf.create_stamp("my_stamp") {
24
- # pdf.fill_circle_at([10, 15], :radius => 5)
24
+ # pdf.fill_circle([10, 15], 5)
25
25
  # pdf.draw_text("hello world", :at => [20, 10])
26
26
  # }
27
27
  # pdf.stamp("my_stamp")
@@ -35,7 +35,7 @@ module Prawn
35
35
  #
36
36
  # Example:
37
37
  # pdf.create_stamp("my_stamp") {
38
- # pdf.fill_circle_at([10, 15], :radius => 5)
38
+ # pdf.fill_circle([10, 15], 5)
39
39
  # pdf.text("hello world", :at => [20, 10])
40
40
  # }
41
41
  # pdf.stamp("my_stamp")
@@ -52,7 +52,7 @@ module Prawn
52
52
  #
53
53
  # Example:
54
54
  # pdf.create_stamp("circle") do
55
- # pdf.fill_circle_at([0, 0], :radius => 25)
55
+ # pdf.fill_circle([0, 0], 25)
56
56
  # end
57
57
  # # draws a circle at 100, 100
58
58
  # pdf.stamp_at("circle", [100, 100])
@@ -71,7 +71,7 @@ module Prawn
71
71
  #
72
72
  # Example:
73
73
  # pdf.create_stamp("my_stamp") {
74
- # pdf.fill_circle_at([10, 15], :radius => 5)
74
+ # pdf.fill_circle([10, 15], 5)
75
75
  # pdf.draw_text("hello world", :at => [20, 10])
76
76
  # }
77
77
  #
@@ -121,6 +121,14 @@ module Prawn
121
121
  :stamp_dictionary => dictionary }
122
122
  dictionary
123
123
  end
124
+
125
+ def freeze_stamp_graphics
126
+ update_colors
127
+ write_line_width
128
+ write_stroke_cap_style
129
+ write_stroke_join_style
130
+ write_stroke_dash
131
+ end
124
132
 
125
133
  end
126
134
  end
data/lib/prawn/table.rb CHANGED
@@ -230,12 +230,42 @@ module Prawn
230
230
  ref_bounds = @pdf.bounds.stretchy? ? @pdf.margin_box : @pdf.bounds
231
231
 
232
232
  last_y = @pdf.y
233
+
234
+ # Determine whether we're at the top of the current bounds (margin box or
235
+ # bounding box). If we're at the top, we couldn't gain any more room by
236
+ # breaking to the next page -- this means, in particular, that if the
237
+ # first row is taller than the margin box, we will only move to the next
238
+ # page if we're below the top. Some floating-point tolerance is added to
239
+ # the calculation.
240
+ #
241
+ # Note that we use the actual bounds, not the reference bounds. This is
242
+ # because even if we are in a stretchy bounding box, flowing to the next
243
+ # page will not buy us any space if we are at the top.
244
+ if @pdf.y > @pdf.bounds.height + @pdf.bounds.absolute_bottom - 0.001
245
+ # we're at the top of our bounds
246
+ started_new_page_at_row = 0
247
+ else
248
+ started_new_page_at_row = -1
249
+
250
+ # If there isn't enough room left on the page to fit the first data row
251
+ # (excluding the header), start the table on the next page.
252
+ needed_height = row(0).height
253
+ needed_height += row(1).height if @header
254
+ if needed_height > @pdf.y - ref_bounds.absolute_bottom
255
+ @pdf.bounds.move_past_bottom
256
+ offset = @pdf.y
257
+ started_new_page_at_row = 0
258
+ end
259
+ end
260
+
233
261
  @cells.each do |cell|
234
- if cell.height > (cell.y + offset) - ref_bounds.absolute_bottom
262
+ if cell.height > (cell.y + offset) - ref_bounds.absolute_bottom &&
263
+ cell.row > started_new_page_at_row
235
264
  # start a new page or column
236
265
  @pdf.bounds.move_past_bottom
237
- draw_header
266
+ draw_header unless cell.row == 0
238
267
  offset = @pdf.y - cell.y
268
+ started_new_page_at_row = cell.row
239
269
  end
240
270
 
241
271
  # Don't modify cell.x / cell.y here, as we want to reuse the original
@@ -275,12 +305,14 @@ module Prawn
275
305
  @column_widths ||= begin
276
306
  if width < cells.min_width
277
307
  raise Errors::CannotFit,
278
- "Table's width was set too small to contain its contents"
308
+ "Table's width was set too small to contain its contents " +
309
+ "(min width #{cells.min_width}, requested #{width})"
279
310
  end
280
311
 
281
312
  if width > cells.max_width
282
313
  raise Errors::CannotFit,
283
- "Table's width was set larger than its contents' maximum width"
314
+ "Table's width was set larger than its contents' maximum width " +
315
+ "(max width #{cells.max_width}, requested #{width})"
284
316
  end
285
317
 
286
318
  if width < natural_width
@@ -55,11 +55,17 @@ module Prawn
55
55
 
56
56
  # If provided, the minimum width that this cell will permit.
57
57
  #
58
- attr_reader :min_width
58
+ def min_width
59
+ set_width_constraints
60
+ @min_width
61
+ end
59
62
 
60
63
  # If provided, the maximum width that this cell can be drawn in.
61
64
  #
62
- attr_reader :max_width
65
+ def max_width
66
+ set_width_constraints
67
+ @max_width
68
+ end
63
69
 
64
70
  # Manually specify the cell's height.
65
71
  #
@@ -70,14 +76,13 @@ module Prawn
70
76
  #
71
77
  attr_accessor :borders
72
78
 
73
- # Specifies the width, in PDF points, of the cell's borders.
79
+ # Width, in PDF points, of the cell's borders: [top, right, bottom, left].
74
80
  #
75
- attr_accessor :border_width
81
+ attr_reader :border_widths
76
82
 
77
- # Specifies the color of the cell borders. Given in HTML RGB format, e.g.,
78
- # "ccffff".
83
+ # HTML RGB-format ("ccffff") border colors: [top, right, bottom, left].
79
84
  #
80
- attr_accessor :border_color
85
+ attr_reader :border_colors
81
86
 
82
87
  # Specifies the content for the cell. Must be a "cellable" object. See the
83
88
  # "Data" section of the Prawn::Table documentation for details on cellable
@@ -97,8 +102,15 @@ module Prawn
97
102
  #
98
103
  def self.make(pdf, content, options={})
99
104
  at = options.delete(:at) || [0, pdf.cursor]
100
- content = "" if content.nil?
101
- options[:content] = content
105
+ content = content.to_s if content.nil? || content.kind_of?(Numeric) ||
106
+ content.kind_of?(Date)
107
+
108
+ if content.is_a?(Hash)
109
+ options.update(content)
110
+ content = options[:content]
111
+ else
112
+ options[:content] = content
113
+ end
102
114
 
103
115
  case content
104
116
  when Prawn::Table::Cell
@@ -135,16 +147,29 @@ module Prawn
135
147
  @point = point
136
148
 
137
149
  # Set defaults; these can be changed by options
138
- @padding = [5, 5, 5, 5]
139
- @borders = [:top, :bottom, :left, :right]
140
- @border_width = 1
141
- @border_color = '000000'
150
+ @padding = [5, 5, 5, 5]
151
+ @borders = [:top, :bottom, :left, :right]
152
+ @border_widths = [1] * 4
153
+ @border_colors = ['000000'] * 4
142
154
 
143
155
  options.each { |k, v| send("#{k}=", v) }
156
+ end
157
+
158
+ # Supports setting multiple properties at once.
159
+ #
160
+ # cell.style(:padding => 0, :border_width => 2)
161
+ #
162
+ # is the same as:
163
+ #
164
+ # cell.padding = 0
165
+ # cell.border_width = 2
166
+ #
167
+ def style(options={}, &block)
168
+ options.each { |k, v| send("#{k}=", v) }
144
169
 
145
- # Sensible defaults for min / max.
146
- @min_width = padding_left + padding_right
147
- @max_width = @pdf.bounds.width
170
+ # The block form supports running a single block for multiple cells, as
171
+ # in Cells#style.
172
+ block.call(self) if block
148
173
  end
149
174
 
150
175
  # Returns the cell's width in points, inclusive of padding.
@@ -209,13 +234,15 @@ module Prawn
209
234
  # location at which the cell is drawn.
210
235
  #
211
236
  def draw(pt=[x, y])
237
+ set_width_constraints
238
+
212
239
  draw_background(pt)
240
+ draw_borders(pt)
213
241
  @pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
214
242
  :width => content_width + FPTolerance,
215
243
  :height => content_height + FPTolerance) do
216
244
  draw_content
217
245
  end
218
- draw_borders(pt)
219
246
  end
220
247
 
221
248
  # x-position of the cell within the parent bounds.
@@ -267,19 +294,179 @@ module Prawn
267
294
  end
268
295
  end
269
296
 
297
+ def padding_top
298
+ @padding[0]
299
+ end
300
+
301
+ def padding_top=(val)
302
+ @padding[0] = val
303
+ end
304
+
305
+ def padding_right
306
+ @padding[1]
307
+ end
308
+
309
+ def padding_right=(val)
310
+ @padding[1] = val
311
+ end
312
+
313
+ def padding_bottom
314
+ @padding[2]
315
+ end
316
+
317
+ def padding_bottom=(val)
318
+ @padding[2] = val
319
+ end
320
+
321
+ def padding_left
322
+ @padding[3]
323
+ end
324
+
325
+ def padding_left=(val)
326
+ @padding[3] = val
327
+ end
328
+
329
+ # Sets border colors on this cell. The argument can be one of:
330
+ #
331
+ # * an integer (sets all colors)
332
+ # * a two-element array [vertical, horizontal]
333
+ # * a three-element array [top, horizontal, bottom]
334
+ # * a four-element array [top, right, bottom, left]
335
+ #
336
+ def border_color=(color)
337
+ @border_colors = case
338
+ when color.nil?
339
+ ["000000"] * 4
340
+ when String === color # all colors
341
+ [color, color, color, color]
342
+ when color.length == 2 # vert, horiz
343
+ [color[0], color[1], color[0], color[1]]
344
+ when color.length == 3 # top, horiz, bottom
345
+ [color[0], color[1], color[2], color[1]]
346
+ when color.length == 4 # top, right, bottom, left
347
+ [color[0], color[1], color[2], color[3]]
348
+ else
349
+ raise ArgumentError, ":border_color must be a string " +
350
+ "or an array [v,h] or [t,r,b,l]"
351
+ end
352
+ end
353
+ alias_method :border_colors=, :border_color=
354
+
355
+ def border_top_color
356
+ @border_colors[0]
357
+ end
358
+
359
+ def border_top_color=(val)
360
+ @border_colors[0] = val
361
+ end
362
+
363
+ def border_top_color
364
+ @border_colors[0]
365
+ end
366
+
367
+ def border_top_color=(val)
368
+ @border_colors[0] = val
369
+ end
370
+
371
+ def border_right_color
372
+ @border_colors[1]
373
+ end
374
+
375
+ def border_right_color=(val)
376
+ @border_colors[1] = val
377
+ end
378
+
379
+ def border_bottom_color
380
+ @border_colors[2]
381
+ end
382
+
383
+ def border_bottom_color=(val)
384
+ @border_colors[2] = val
385
+ end
386
+
387
+ def border_left_color
388
+ @border_colors[3]
389
+ end
390
+
391
+ def border_left_color=(val)
392
+ @border_colors[3] = val
393
+ end
394
+
395
+ # Sets border widths on this cell. The argument can be one of:
396
+ #
397
+ # * an integer (sets all widths)
398
+ # * a two-element array [vertical, horizontal]
399
+ # * a three-element array [top, horizontal, bottom]
400
+ # * a four-element array [top, right, bottom, left]
401
+ #
402
+ def border_width=(width)
403
+ @border_widths = case
404
+ when width.nil?
405
+ ["000000"] * 4
406
+ when Numeric === width # all widths
407
+ [width, width, width, width]
408
+ when width.length == 2 # vert, horiz
409
+ [width[0], width[1], width[0], width[1]]
410
+ when width.length == 3 # top, horiz, bottom
411
+ [width[0], width[1], width[2], width[1]]
412
+ when width.length == 4 # top, right, bottom, left
413
+ [width[0], width[1], width[2], width[3]]
414
+ else
415
+ raise ArgumentError, ":border_width must be a string " +
416
+ "or an array [v,h] or [t,r,b,l]"
417
+ end
418
+ end
419
+ alias_method :border_widths=, :border_width=
420
+
421
+ def border_top_width
422
+ @borders.include?(:top) ? @border_widths[0] : 0
423
+ end
424
+
425
+ def border_top_width=(val)
426
+ @border_widths[0] = val
427
+ end
428
+
429
+ def border_right_width
430
+ @borders.include?(:right) ? @border_widths[1] : 0
431
+ end
432
+
433
+ def border_right_width=(val)
434
+ @border_widths[1] = val
435
+ end
436
+
437
+ def border_bottom_width
438
+ @borders.include?(:bottom) ? @border_widths[2] : 0
439
+ end
440
+
441
+ def border_bottom_width=(val)
442
+ @border_widths[2] = val
443
+ end
444
+
445
+ def border_left_width
446
+ @borders.include?(:left) ? @border_widths[3] : 0
447
+ end
448
+
449
+ def border_left_width=(val)
450
+ @border_widths[3] = val
451
+ end
452
+
270
453
  protected
271
454
 
455
+ # Sets the cell's minimum and maximum width. Deferred until requested
456
+ # because padding and size can change.
457
+ #
458
+ def set_width_constraints
459
+ @min_width ||= padding_left + padding_right
460
+ @max_width ||= @pdf.bounds.width
461
+ end
462
+
272
463
  # Draws the cell's background color.
273
464
  #
274
465
  def draw_background(pt)
275
- x, y = pt
276
- margin = @border_width / 2
277
466
  if @background_color
278
467
  @pdf.mask(:fill_color) do
279
468
  @pdf.fill_color @background_color
280
- h = @borders.include?(:bottom) ? height - (2*margin) :
281
- height + margin
282
- @pdf.fill_rectangle [x, y], width, h
469
+ @pdf.fill_rectangle pt, width, height
283
470
  end
284
471
  end
285
472
  end
@@ -291,26 +478,32 @@ module Prawn
291
478
  #
292
479
  def draw_borders(pt)
293
480
  x, y = pt
294
- return if @border_width <= 0
295
- # Draw left / right borders one-half border width beyond the center of
296
- # the corner, so that the corners end up square.
297
- margin = @border_width / 2.0
298
481
 
299
482
  @pdf.mask(:line_width, :stroke_color) do
300
- @pdf.line_width = @border_width
301
- @pdf.stroke_color = @border_color if @border_color
302
-
303
483
  @borders.each do |border|
484
+ idx = {:top => 0, :right => 1, :bottom => 2, :left => 3}[border]
485
+ border_color = @border_colors[idx]
486
+ border_width = @border_widths[idx]
487
+
488
+ next if border_width <= 0
489
+
490
+ # Left and right borders are drawn one-half border beyond the center
491
+ # of the corner, so that the corners end up square.
304
492
  from, to = case border
305
493
  when :top
306
494
  [[x, y], [x+width, y]]
307
495
  when :bottom
308
496
  [[x, y-height], [x+width, y-height]]
309
497
  when :left
310
- [[x, y+margin], [x, y-height-margin]]
498
+ [[x, y + (border_top_width / 2.0)],
499
+ [x, y - height - (border_bottom_width / 2.0)]]
311
500
  when :right
312
- [[x+width, y+margin], [x+width, y-height-margin]]
501
+ [[x+width, y + (border_top_width / 2.0)],
502
+ [x+width, y - height - (border_bottom_width / 2.0)]]
313
503
  end
504
+
505
+ @pdf.line_width = border_width
506
+ @pdf.stroke_color = border_color
314
507
  @pdf.stroke_line(from, to)
315
508
  end
316
509
  end
@@ -323,38 +516,6 @@ module Prawn
323
516
  raise NotImplementedError, "subclasses must implement draw_content"
324
517
  end
325
518
 
326
- def padding_top
327
- @padding[0]
328
- end
329
-
330
- def padding_top=(val)
331
- @padding[0] = val
332
- end
333
-
334
- def padding_right
335
- @padding[1]
336
- end
337
-
338
- def padding_right=(val)
339
- @padding[1] = val
340
- end
341
-
342
- def padding_bottom
343
- @padding[2]
344
- end
345
-
346
- def padding_bottom=(val)
347
- @padding[2] = val
348
- end
349
-
350
- def padding_left
351
- @padding[3]
352
- end
353
-
354
- def padding_left=(val)
355
- @padding[3] = val
356
- end
357
-
358
519
  end
359
520
  end
360
521
  end