prawn 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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