prawn 0.11.1 → 0.12.0

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 (106) hide show
  1. data/GPLv2 +340 -0
  2. data/GPLv3 +674 -0
  3. data/README.md +96 -0
  4. data/Rakefile +0 -12
  5. data/examples/example_helper.rb +3 -0
  6. data/lib/prawn.rb +1 -1
  7. data/lib/prawn/core/page.rb +4 -2
  8. data/lib/prawn/core/pdf_object.rb +7 -7
  9. data/lib/prawn/core/text/formatted/arranger.rb +2 -1
  10. data/lib/prawn/core/text/formatted/line_wrap.rb +2 -1
  11. data/lib/prawn/core/text/formatted/wrap.rb +5 -1
  12. data/lib/prawn/document.rb +135 -112
  13. data/lib/prawn/document/bounding_box.rb +51 -5
  14. data/lib/prawn/document/column_box.rb +0 -19
  15. data/lib/prawn/document/internals.rb +1 -0
  16. data/lib/prawn/document/snapshot.rb +3 -0
  17. data/lib/prawn/font.rb +4 -3
  18. data/lib/prawn/font/afm.rb +11 -2
  19. data/lib/prawn/font/ttf.rb +15 -0
  20. data/lib/prawn/images.rb +1 -1
  21. data/lib/prawn/table.rb +23 -7
  22. data/lib/prawn/table/cell.rb +31 -9
  23. data/lib/prawn/table/cell/text.rb +2 -2
  24. data/prawn.gemspec +5 -6
  25. data/spec/bounding_box_spec.rb +157 -0
  26. data/spec/data/curves.pdf +66 -0
  27. data/spec/document_spec.rb +59 -4
  28. data/spec/extensions/mocha.rb +1 -3
  29. data/spec/font_spec.rb +23 -0
  30. data/spec/formatted_text_box_spec.rb +40 -0
  31. data/spec/images_spec.rb +7 -0
  32. data/spec/object_store_spec.rb +3 -3
  33. data/spec/snapshot_spec.rb +33 -0
  34. data/spec/table_spec.rb +75 -0
  35. data/spec/template_spec.rb +11 -5
  36. data/spec/text_spacing_spec.rb +18 -0
  37. data/spec/text_spec.rb +32 -0
  38. metadata +217 -283
  39. data/HACKING +0 -50
  40. data/README +0 -141
  41. data/examples/bounding_box/bounding_boxes.rb +0 -44
  42. data/examples/bounding_box/indentation.rb +0 -35
  43. data/examples/bounding_box/stretched_nesting.rb +0 -68
  44. data/examples/general/background.rb +0 -24
  45. data/examples/general/canvas.rb +0 -16
  46. data/examples/general/float.rb +0 -12
  47. data/examples/general/margin.rb +0 -37
  48. data/examples/general/measurement_units.rb +0 -52
  49. data/examples/general/metadata-info.rb +0 -17
  50. data/examples/general/multi_page_layout.rb +0 -19
  51. data/examples/general/outlines.rb +0 -67
  52. data/examples/general/page_geometry.rb +0 -32
  53. data/examples/general/page_numbering.rb +0 -40
  54. data/examples/general/page_templates.rb +0 -20
  55. data/examples/general/repeaters.rb +0 -48
  56. data/examples/general/stamp.rb +0 -42
  57. data/examples/general/templates.rb +0 -14
  58. data/examples/graphics/basic_images.rb +0 -24
  59. data/examples/graphics/curves.rb +0 -12
  60. data/examples/graphics/hexagon.rb +0 -14
  61. data/examples/graphics/image_fit.rb +0 -16
  62. data/examples/graphics/image_flow.rb +0 -38
  63. data/examples/graphics/image_position.rb +0 -18
  64. data/examples/graphics/line.rb +0 -33
  65. data/examples/graphics/polygons.rb +0 -17
  66. data/examples/graphics/rounded_polygons.rb +0 -20
  67. data/examples/graphics/rounded_rectangle.rb +0 -21
  68. data/examples/graphics/ruport_style_helpers.rb +0 -20
  69. data/examples/graphics/stroke_bounds.rb +0 -21
  70. data/examples/graphics/stroke_cap_and_join.rb +0 -46
  71. data/examples/graphics/stroke_dash.rb +0 -43
  72. data/examples/graphics/transformations.rb +0 -53
  73. data/examples/graphics/transparency.rb +0 -27
  74. data/examples/grid/bounding_boxes.rb +0 -22
  75. data/examples/grid/column_gutter_grid.rb +0 -21
  76. data/examples/grid/multi_boxes.rb +0 -52
  77. data/examples/grid/show_grid.rb +0 -14
  78. data/examples/grid/simple_grid.rb +0 -21
  79. data/examples/m17n/chinese_text_wrapping.rb +0 -18
  80. data/examples/m17n/euro.rb +0 -16
  81. data/examples/m17n/utf8.rb +0 -14
  82. data/examples/security/hello_foo.rb +0 -9
  83. data/examples/table/borders.rb +0 -25
  84. data/examples/table/cell.rb +0 -13
  85. data/examples/table/checkerboard.rb +0 -23
  86. data/examples/table/inline_format_table.rb +0 -13
  87. data/examples/table/multi_page_table.rb +0 -10
  88. data/examples/table/simple_table.rb +0 -25
  89. data/examples/table/subtable.rb +0 -13
  90. data/examples/table/widths.rb +0 -21
  91. data/examples/text/alignment.rb +0 -19
  92. data/examples/text/character_spacing.rb +0 -13
  93. data/examples/text/dfont.rb +0 -49
  94. data/examples/text/family_based_styling.rb +0 -25
  95. data/examples/text/font_size.rb +0 -34
  96. data/examples/text/inline_format.rb +0 -104
  97. data/examples/text/kerning.rb +0 -31
  98. data/examples/text/rendering_mode.rb +0 -21
  99. data/examples/text/rotated.rb +0 -99
  100. data/examples/text/shaped_text_box.rb +0 -32
  101. data/examples/text/simple_text.rb +0 -18
  102. data/examples/text/simple_text_ttf.rb +0 -18
  103. data/examples/text/span.rb +0 -30
  104. data/examples/text/text_box.rb +0 -90
  105. data/examples/text/text_box_returning_excess.rb +0 -52
  106. data/examples/text/text_flow.rb +0 -68
@@ -181,13 +181,20 @@ module Prawn
181
181
  private
182
182
 
183
183
  def init_bounding_box(user_block, options={}, &init_block)
184
+ unless user_block
185
+ raise ArgumentError,
186
+ "bounding boxes require a block to be drawn within the box"
187
+ end
188
+
184
189
  parent_box = @bounding_box
185
190
 
186
191
  init_block.call(parent_box)
187
192
 
188
193
  self.y = @bounding_box.absolute_top
189
194
  user_block.call
190
- self.y = @bounding_box.absolute_bottom unless options[:hold_position]
195
+ unless options[:hold_position] || @bounding_box.stretchy?
196
+ self.y = @bounding_box.absolute_bottom
197
+ end
191
198
 
192
199
  created_box, @bounding_box = @bounding_box, parent_box
193
200
 
@@ -435,17 +442,23 @@ module Prawn
435
442
 
436
443
  # an alias for absolute_left
437
444
  def left_side
438
- absolute_left
445
+ absolute_left
439
446
  end
440
447
 
441
448
  # an alias for absolute_right
442
449
  def right_side
443
- absolute_right
450
+ absolute_right
444
451
  end
445
452
 
446
- # starts a new page
453
+ # Moves to the top of the next page of the document, starting a new page
454
+ # if necessary.
455
+ #
447
456
  def move_past_bottom
448
- @document.start_new_page
457
+ if @document.page_number == @document.page_count
458
+ @document.start_new_page
459
+ else
460
+ @document.go_to_page(@document.page_number + 1)
461
+ end
449
462
  end
450
463
 
451
464
 
@@ -458,6 +471,39 @@ module Prawn
458
471
  !@height
459
472
  end
460
473
 
474
+ # Returns the innermost non-stretchy bounding box.
475
+ #
476
+ def reference_bounds
477
+ if stretchy?
478
+ raise "Can't find reference bounds: my parent is unset" unless @parent
479
+ @parent.reference_bounds
480
+ else
481
+ self
482
+ end
483
+ end
484
+
485
+ # Returns a deep copy of these bounds (including all parent bounds but
486
+ # not copying the reference to the Document).
487
+ #
488
+ def deep_copy
489
+ copy = dup
490
+ # Deep-copy the parent bounds
491
+ copy.instance_variable_set("@parent", if BoundingBox === @parent
492
+ @parent.deep_copy
493
+ end)
494
+ copy.instance_variable_set("@document", nil)
495
+ copy
496
+ end
497
+
498
+ # Restores a copy of the bounds taken by BoundingBox.deep_copy in the
499
+ # context of the given +document+. Does *not* set the bounds of the
500
+ # document to the resulting BoundingBox, only returns it.
501
+ #
502
+ def self.restore_deep_copy(bounds, document)
503
+ bounds.instance_variable_set("@document", document)
504
+ bounds
505
+ end
506
+
461
507
  end
462
508
 
463
509
  end
@@ -41,25 +41,6 @@ module Prawn
41
41
  @bounding_box = parent_box
42
42
  end
43
43
 
44
- # Template methods to support ColumnBox extensions
45
- class BoundingBox
46
-
47
- # an alias for absolute_left
48
- def left_side
49
- absolute_left
50
- end
51
-
52
- # an alias for absolute_right
53
- def right_side
54
- absolute_right
55
- end
56
-
57
- # starts a new page
58
- def move_past_bottom
59
- @document.start_new_page
60
- end
61
- end
62
-
63
44
  # Implements the necessary functionality to allow Document#column_box to
64
45
  # work.
65
46
  #
@@ -98,6 +98,7 @@ module Prawn
98
98
  go_to_page i
99
99
  state.page.new_content_stream
100
100
  apply_margin_options(options)
101
+ generate_margin_box
101
102
  use_graphic_settings(options[:template])
102
103
  end
103
104
  end
@@ -51,6 +51,7 @@ module Prawn
51
51
  # between the old and new copies.
52
52
  {:page_content => state.page.content.deep_copy,
53
53
  :current_page => state.page.dictionary.deep_copy(share=[:Parent]),
54
+ :bounds => bounds.deep_copy,
54
55
  :page_number => page_number,
55
56
  :page_kids => state.store.pages.data[:Kids].map{|kid| kid.identifier},
56
57
  :dests => names? &&
@@ -77,6 +78,8 @@ module Prawn
77
78
  state.store.pages.data[:Kids] = shot[:page_kids].map{|id| state.store[id]}
78
79
  state.store.pages.data[:Count] = shot[:page_kids].size
79
80
 
81
+ self.bounds = BoundingBox.restore_deep_copy(shot[:bounds], self)
82
+
80
83
  if shot[:dests]
81
84
  names.data[:Dests] = shot[:dests]
82
85
  end
@@ -52,7 +52,7 @@ module Prawn
52
52
  raise Prawn::Errors::NotOnPage
53
53
  end
54
54
 
55
- new_font = find_font(name, options)
55
+ new_font = find_font(name.to_s, options)
56
56
 
57
57
  if block_given?
58
58
  save_font do
@@ -179,7 +179,7 @@ module Prawn
179
179
  # custom ones, like :thin, and use them in font calls.
180
180
  #
181
181
  def font_families
182
- @font_families ||= Hash.new { |h,k| h[k] = {} }.merge!(
182
+ @font_families ||= Hash.new.merge!(
183
183
  { "Courier" => { :bold => "Courier-Bold",
184
184
  :italic => "Courier-Oblique",
185
185
  :bold_italic => "Courier-BoldOblique",
@@ -217,7 +217,8 @@ module Prawn
217
217
  # it and redefine the width calculation behavior.
218
218
  #++
219
219
  def width_of(string, options={})
220
- font.compute_width_of(string, options) + character_spacing * string.length
220
+ font.compute_width_of(string, options) +
221
+ (character_spacing * font.character_count(string))
221
222
  end
222
223
  end
223
224
 
@@ -94,6 +94,12 @@ module Prawn
94
94
  "Arguments to text methods must be UTF-8 encoded"
95
95
  end
96
96
 
97
+ # Returns the number of characters in +str+ (a WinAnsi-encoded string).
98
+ #
99
+ def character_count(str)
100
+ str.length
101
+ end
102
+
97
103
  # Perform any changes to the string that need to happen
98
104
  # before it is rendered to the canvas. Returns an array of
99
105
  # subset "chunks", where each chunk is an array of two elements.
@@ -210,8 +216,11 @@ module Prawn
210
216
  end
211
217
 
212
218
  def latin_kern_pairs_table
213
- @kern_pairs_table ||= @kern_pairs.inject({}) do |h,p|
214
- h[p[0].map { |n| Encoding::WinAnsi::CHARACTERS.index(n) }] = p[1]
219
+ return @kern_pairs_table if defined?(@kern_pairs_table)
220
+
221
+ character_hash = Hash[Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a)]
222
+ @kern_pairs_table = @kern_pairs.inject({}) do |h,p|
223
+ h[p[0].map { |n| character_hash[n] }] = p[1]
215
224
  h
216
225
  end
217
226
  end
@@ -190,6 +190,16 @@ module Prawn
190
190
  cmap[code] > 0
191
191
  end
192
192
 
193
+ # Returns the number of characters in +str+ (a UTF-8-encoded string).
194
+ #
195
+ def character_count(str)
196
+ if str.respond_to?(:encode)
197
+ str.length
198
+ else
199
+ str.unpack("U*").length
200
+ end
201
+ end
202
+
193
203
  private
194
204
 
195
205
  def cmap
@@ -233,6 +243,11 @@ module Prawn
233
243
 
234
244
  def character_width_by_code(code)
235
245
  return 0 unless cmap[code]
246
+
247
+ # Some TTF fonts have nonzero widths for \n (UTF-8 / ASCII code: 10).
248
+ # Patch around this as we'll never be drawing a newline with a width.
249
+ return 0.0 if code == 10
250
+
236
251
  @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
237
252
  end
238
253
 
@@ -158,7 +158,7 @@ module Prawn
158
158
  end
159
159
 
160
160
  def overruns_page?(h)
161
- (self.y - h) < bounds.absolute_bottom
161
+ (self.y - h) < reference_bounds.absolute_bottom
162
162
  end
163
163
 
164
164
  def calc_image_dimensions(info, options)
@@ -121,6 +121,7 @@ module Prawn
121
121
  @pdf = document
122
122
  @cells = make_cells(data)
123
123
  @header = false
124
+ @epsilon = 1e-9
124
125
  options.each { |k, v| send("#{k}=", v) }
125
126
 
126
127
  if block
@@ -168,7 +169,7 @@ module Prawn
168
169
  when Hash
169
170
  widths.each { |i, w| column(i).width = w }
170
171
  when Numeric
171
- columns.width = widths
172
+ cells.width = widths
172
173
  else
173
174
  raise ArgumentError, "cannot interpret column widths"
174
175
  end
@@ -227,7 +228,7 @@ module Prawn
227
228
 
228
229
  # Reference bounds are the non-stretchy bounds used to decide when to
229
230
  # flow to a new column / page.
230
- ref_bounds = @pdf.bounds.stretchy? ? @pdf.margin_box : @pdf.bounds
231
+ ref_bounds = @pdf.reference_bounds
231
232
 
232
233
  last_y = @pdf.y
233
234
 
@@ -258,9 +259,17 @@ module Prawn
258
259
  end
259
260
  end
260
261
 
262
+ # Track cells to be drawn on this page. They will all be drawn when this
263
+ # page is finished.
264
+ cells_this_page = []
265
+
261
266
  @cells.each do |cell|
262
267
  if cell.height > (cell.y + offset) - ref_bounds.absolute_bottom &&
263
268
  cell.row > started_new_page_at_row
269
+ # Ink all cells on the current page
270
+ Cell.draw_cells(cells_this_page)
271
+ cells_this_page = []
272
+
264
273
  # start a new page or column
265
274
  @pdf.bounds.move_past_bottom
266
275
  draw_header unless cell.row == 0
@@ -285,9 +294,11 @@ module Prawn
285
294
  cell.background_color = @row_colors[index % @row_colors.length]
286
295
  end
287
296
 
288
- cell.draw([x, y])
297
+ cells_this_page << [cell, [x, y]]
289
298
  last_y = y
290
299
  end
300
+ # Draw the last page of cells
301
+ Cell.draw_cells(cells_this_page)
291
302
 
292
303
  @pdf.move_cursor_to(last_y - @cells.last.height)
293
304
  end
@@ -303,19 +314,19 @@ module Prawn
303
314
  #
304
315
  def column_widths
305
316
  @column_widths ||= begin
306
- if width < cells.min_width
317
+ if width - cells.min_width < -epsilon
307
318
  raise Errors::CannotFit,
308
319
  "Table's width was set too small to contain its contents " +
309
320
  "(min width #{cells.min_width}, requested #{width})"
310
321
  end
311
322
 
312
- if width > cells.max_width
323
+ if width - cells.max_width > epsilon
313
324
  raise Errors::CannotFit,
314
325
  "Table's width was set larger than its contents' maximum width " +
315
326
  "(max width #{cells.max_width}, requested #{width})"
316
327
  end
317
328
 
318
- if width < natural_width
329
+ if width - natural_width < -epsilon
319
330
  # Shrink the table to fit the requested width.
320
331
  f = (width - cells.min_width).to_f / (natural_width - cells.min_width)
321
332
 
@@ -323,7 +334,7 @@ module Prawn
323
334
  min, nat = column(c).min_width, column(c).width
324
335
  (f * (nat - min)) + min
325
336
  end
326
- elsif width > natural_width
337
+ elsif width - natural_width > epsilon
327
338
  # Expand the table to fit the requested width.
328
339
  f = (width - cells.width).to_f / (cells.max_width - cells.width)
329
340
 
@@ -447,6 +458,11 @@ module Prawn
447
458
  y_positions.each_with_index { |y, i| row(i).y = y }
448
459
  end
449
460
 
461
+ private
462
+
463
+ def epsilon
464
+ @epsilon
465
+ end
450
466
  end
451
467
 
452
468
 
@@ -233,15 +233,39 @@ module Prawn
233
233
  # Draws the cell onto the document. Pass in a point [x,y] to override the
234
234
  # location at which the cell is drawn.
235
235
  #
236
+ # If drawing a group of cells at known positions, look into
237
+ # Cell.draw_cells, which ensures that the backgrounds, borders, and
238
+ # content are all drawn in correct order so as not to overlap.
239
+ #
236
240
  def draw(pt=[x, y])
237
- set_width_constraints
241
+ self.class.draw_cells([[self, pt]])
242
+ end
243
+
244
+ # Given an array of pairs [cell, pt], draws each cell at its
245
+ # corresponding pt, making sure all backgrounds are behind all borders
246
+ # and content.
247
+ #
248
+ def self.draw_cells(cells)
249
+ cells.each do |cell, pt|
250
+ cell.set_width_constraints
251
+ cell.draw_background(pt)
252
+ end
238
253
 
239
- draw_background(pt)
240
- draw_borders(pt)
241
- @pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
242
- :width => content_width + FPTolerance,
243
- :height => content_height + FPTolerance) do
244
- draw_content
254
+ cells.each do |cell, pt|
255
+ cell.draw_borders(pt)
256
+ cell.draw_bounded_content(pt)
257
+ end
258
+ end
259
+
260
+ # Draws the cell's content at the point provided.
261
+ #
262
+ def draw_bounded_content(pt)
263
+ @pdf.float do
264
+ @pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
265
+ :width => content_width + FPTolerance,
266
+ :height => content_height + FPTolerance) do
267
+ draw_content
268
+ end
245
269
  end
246
270
  end
247
271
 
@@ -450,8 +474,6 @@ module Prawn
450
474
  @border_widths[3] = val
451
475
  end
452
476
 
453
- protected
454
-
455
477
  # Sets the cell's minimum and maximum width. Deferred until requested
456
478
  # because padding and size can change.
457
479
  #
@@ -74,8 +74,6 @@ module Prawn
74
74
  end
75
75
  end
76
76
 
77
- protected
78
-
79
77
  def set_width_constraints
80
78
  # Sets a reasonable minimum width. If the cell has any content, make
81
79
  # sure we have enough width to be at least one character wide. This is
@@ -85,6 +83,8 @@ module Prawn
85
83
  super
86
84
  end
87
85
 
86
+ protected
87
+
88
88
  def with_font
89
89
  @pdf.save_font do
90
90
  options = {}
@@ -1,5 +1,5 @@
1
1
  # Version numbering: http://wiki.github.com/sandal/prawn/development-roadmap
2
- PRAWN_VERSION = "0.11.1"
2
+ PRAWN_VERSION = "0.12.0"
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "prawn"
@@ -12,16 +12,15 @@ Gem::Specification.new do |spec|
12
12
  spec.required_ruby_version = '>= 1.8.7'
13
13
  spec.required_rubygems_version = ">= 1.3.6"
14
14
 
15
- spec.test_files = Dir[ "test/*_test.rb" ]
16
- spec.has_rdoc = true
17
- spec.extra_rdoc_files = %w{HACKING README LICENSE COPYING}
15
+ spec.test_files = Dir[ "spec/*_spec.rb" ]
16
+ spec.extra_rdoc_files = %w{README.md LICENSE COPYING GPLv2 GPLv3}
18
17
  spec.rdoc_options << '--title' << 'Prawn Documentation' <<
19
18
  '--main' << 'README' << '-q'
20
- spec.authors = ["Gregory Brown","Brad Ediger","Daniel Nelson","Jonathen Greenberg","James Healy"]
19
+ spec.authors = ["Gregory Brown","Brad Ediger","Daniel Nelson","Jonathan Greenberg","James Healy"]
21
20
  spec.email = ["gregory.t.brown@gmail.com","brad@bradediger.com","dnelson@bluejade.com","greenberg@entryway.net","jimmy@deefa.com"]
22
21
  spec.rubyforge_project = "prawn"
23
22
  spec.add_dependency('pdf-reader', '>=0.9.0')
24
- spec.add_dependency('ttfunk', '~>1.0.0')
23
+ spec.add_dependency('ttfunk', '~>1.0.2')
25
24
  spec.homepage = "http://prawn.majesticseacreature.com"
26
25
  spec.description = <<END_DESC
27
26
  Prawn is a fast, tiny, and nimble PDF generator for Ruby
@@ -87,6 +87,13 @@ describe "A bounding box" do
87
87
  end.should.raise(ArgumentError)
88
88
  end
89
89
 
90
+ it "should raise an ArgumentError if a block is not passed" do
91
+ pdf = Prawn::Document.new
92
+ lambda do
93
+ pdf.bounding_box([0, 0], :width => 200)
94
+ end.should.raise(ArgumentError)
95
+ end
96
+
90
97
  end
91
98
 
92
99
  describe "drawing bounding boxes" do
@@ -140,6 +147,44 @@ describe "drawing bounding boxes" do
140
147
  box.height.should == 100
141
148
  end
142
149
 
150
+ it "should advance the y-position by bbox.height by default" do
151
+ orig_y = @pdf.y
152
+ @pdf.bounding_box [0, @pdf.cursor], :width => @pdf.bounds.width,
153
+ :height => 30 do
154
+ @pdf.text "hello"
155
+ end
156
+ @pdf.y.should.be.close(orig_y - 30, 0.001)
157
+ end
158
+
159
+ it "should not advance y-position if passed :hold_position => true" do
160
+ orig_y = @pdf.y
161
+ @pdf.bounding_box [0, @pdf.cursor], :width => @pdf.bounds.width,
162
+ :hold_position => true do
163
+ @pdf.text "hello"
164
+ end
165
+ # y only advances by height of one line ("hello")
166
+ @pdf.y.should.be.close(orig_y - @pdf.height_of("hello"), 0.001)
167
+ end
168
+
169
+ it "should not advance y-position of a stretchy bbox if it would stretch " +
170
+ "the bbox further" do
171
+ bottom = @pdf.y = @pdf.margin_box.absolute_bottom
172
+ @pdf.bounding_box [0, @pdf.margin_box.top], :width => @pdf.bounds.width do
173
+ @pdf.y = bottom
174
+ @pdf.text "hello" # starts a new page
175
+ end
176
+ @pdf.page_count.should == 2
177
+
178
+ # Restoring the position (to the absolute bottom) would stretch the bbox to
179
+ # the bottom of the page, which we don't want. This should be equivalent to
180
+ # a bbox with :hold_position => true, where we only advance by the amount
181
+ # that was actually drawn.
182
+ @pdf.y.should.be.close(
183
+ @pdf.margin_box.absolute_top - @pdf.height_of("hello"),
184
+ 0.001
185
+ )
186
+ end
187
+
143
188
  end
144
189
 
145
190
  describe "Indentation" do
@@ -221,3 +266,115 @@ describe "A canvas" do
221
266
  @pdf.y.should == 450
222
267
  end
223
268
  end
269
+
270
+ describe "Deep-copying" do
271
+ it "should create a new object that does not copy @document" do
272
+ Prawn::Document.new do
273
+ orig = bounds
274
+ copy = orig.deep_copy
275
+
276
+ copy.should.not == bounds
277
+ copy.document.should.be.nil
278
+ end
279
+ end
280
+
281
+ it "should deep-copy parent bounds" do
282
+ Prawn::Document.new do |pdf|
283
+ outside = pdf.bounds
284
+ pdf.bounding_box [100, 100], :width => 100 do
285
+ copy = pdf.bounds.deep_copy
286
+
287
+ # the parent bounds should have the same parameters
288
+ copy.parent.width.should == outside.width
289
+ copy.parent.height.should == outside.height
290
+
291
+ # but should not be the same object
292
+ copy.parent.should.not == outside
293
+ end
294
+ end
295
+ end
296
+ end
297
+
298
+ describe "Prawn::Document#reference_bounds" do
299
+ before(:each) { create_pdf }
300
+
301
+ it "should return self for non-stretchy bounds" do
302
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100, :height => 100) do
303
+ @pdf.reference_bounds.should == @pdf.bounds
304
+ end
305
+ end
306
+
307
+ it "should return the parent bounds if in a stretchy box" do
308
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100, :height => 100) do
309
+ correct_bounds = @pdf.bounds
310
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100) do
311
+ @pdf.reference_bounds.should == correct_bounds
312
+ end
313
+ end
314
+ end
315
+
316
+ it "should find the non-stretchy box through 2 levels" do
317
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100, :height => 100) do
318
+ correct_bounds = @pdf.bounds
319
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100) do
320
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100) do
321
+ @pdf.reference_bounds.should == correct_bounds
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ it "should return the margin box if there's no explicit bbox" do
328
+ @pdf.reference_bounds.should == @pdf.margin_box
329
+
330
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100) do
331
+ @pdf.reference_bounds.should == @pdf.margin_box
332
+ end
333
+ end
334
+
335
+ it "should return the canvas box if we're in a canvas" do
336
+ @pdf.canvas do
337
+ canvas_box = @pdf.bounds
338
+
339
+ @pdf.reference_bounds.should == canvas_box
340
+
341
+ @pdf.bounding_box([0, @pdf.cursor], :width => 100) do
342
+ @pdf.reference_bounds.should == canvas_box
343
+ end
344
+ end
345
+ end
346
+
347
+ end
348
+
349
+ describe "BoundingBox#move_past_bottom" do
350
+ before(:each) { create_pdf }
351
+
352
+ it "should ordinarily start a new page" do
353
+ @pdf.bounds.move_past_bottom
354
+ @pdf.text "Foo"
355
+
356
+ pages = PDF::Inspector::Page.analyze(@pdf.render).pages
357
+ pages.size.should == 2
358
+ pages[0][:strings].should == []
359
+ pages[1][:strings].should == ["Foo"]
360
+ end
361
+
362
+ it "should move to the top of the next page if it exists already" do
363
+ # save away the y-position at the top of a page
364
+ top_y = @pdf.y
365
+
366
+ # create a blank page but go to the page before it
367
+ @pdf.start_new_page
368
+ @pdf.go_to_page 1
369
+ @pdf.text "Foo"
370
+
371
+ @pdf.bounds.move_past_bottom
372
+ @pdf.y.should.be.close(top_y, 0.001) # we should be at the top
373
+ @pdf.text "Bar"
374
+
375
+ pages = PDF::Inspector::Page.analyze(@pdf.render).pages
376
+ pages.size.should == 2
377
+ pages[0][:strings].should == ["Foo"]
378
+ pages[1][:strings].should == ["Bar"]
379
+ end
380
+ end