prawn-core 0.7.2 → 0.8.4

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 (65) hide show
  1. data/Rakefile +1 -1
  2. data/examples/general/background.rb +1 -1
  3. data/examples/general/measurement_units.rb +2 -2
  4. data/examples/general/outlines.rb +50 -0
  5. data/examples/general/repeaters.rb +11 -7
  6. data/examples/general/stamp.rb +6 -6
  7. data/examples/graphics/basic_images.rb +1 -1
  8. data/examples/graphics/curves.rb +1 -1
  9. data/examples/graphics/rounded_polygons.rb +19 -0
  10. data/examples/graphics/rounded_rectangle.rb +20 -0
  11. data/examples/graphics/transformations.rb +52 -0
  12. data/examples/m17n/win_ansi_charset.rb +1 -1
  13. data/examples/text/font_calculations.rb +3 -3
  14. data/examples/text/indent_paragraphs.rb +18 -0
  15. data/examples/text/kerning.rb +4 -4
  16. data/examples/text/rotated.rb +98 -0
  17. data/examples/text/simple_text.rb +3 -3
  18. data/examples/text/simple_text_ttf.rb +1 -1
  19. data/lib/prawn/byte_string.rb +1 -0
  20. data/lib/prawn/core.rb +12 -5
  21. data/lib/prawn/core/object_store.rb +99 -0
  22. data/lib/prawn/core/page.rb +96 -0
  23. data/lib/prawn/core/text.rb +75 -0
  24. data/lib/prawn/document.rb +71 -78
  25. data/lib/prawn/document/annotations.rb +2 -2
  26. data/lib/prawn/document/bounding_box.rb +19 -9
  27. data/lib/prawn/document/column_box.rb +13 -12
  28. data/lib/prawn/document/graphics_state.rb +49 -0
  29. data/lib/prawn/document/internals.rb +5 -40
  30. data/lib/prawn/document/page_geometry.rb +1 -18
  31. data/lib/prawn/document/snapshot.rb +12 -7
  32. data/lib/prawn/errors.rb +18 -0
  33. data/lib/prawn/font.rb +4 -2
  34. data/lib/prawn/font/afm.rb +8 -0
  35. data/lib/prawn/font/dfont.rb +12 -4
  36. data/lib/prawn/font/ttf.rb +9 -0
  37. data/lib/prawn/graphics.rb +66 -9
  38. data/lib/prawn/graphics/color.rb +1 -1
  39. data/lib/prawn/graphics/transformation.rb +156 -0
  40. data/lib/prawn/graphics/transparency.rb +3 -7
  41. data/lib/prawn/images.rb +4 -3
  42. data/lib/prawn/images/png.rb +2 -2
  43. data/lib/prawn/outline.rb +278 -0
  44. data/lib/prawn/pdf_object.rb +5 -3
  45. data/lib/prawn/repeater.rb +25 -13
  46. data/lib/prawn/stamp.rb +6 -29
  47. data/lib/prawn/text.rb +139 -121
  48. data/lib/prawn/text/box.rb +168 -102
  49. data/spec/bounding_box_spec.rb +7 -2
  50. data/spec/document_spec.rb +7 -5
  51. data/spec/font_spec.rb +9 -1
  52. data/spec/graphics_spec.rb +229 -0
  53. data/spec/object_store_spec.rb +5 -5
  54. data/spec/outline_spec.rb +229 -0
  55. data/spec/repeater_spec.rb +18 -1
  56. data/spec/snapshot_spec.rb +7 -7
  57. data/spec/span_spec.rb +6 -2
  58. data/spec/spec_helper.rb +7 -3
  59. data/spec/stamp_spec.rb +13 -0
  60. data/spec/text_at_spec.rb +119 -0
  61. data/spec/text_box_spec.rb +257 -4
  62. data/spec/text_spec.rb +278 -180
  63. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +12 -0
  64. metadata +16 -3
  65. data/lib/prawn/object_store.rb +0 -92
@@ -12,6 +12,14 @@ describe "Font behavior" do
12
12
 
13
13
  end
14
14
 
15
+ describe "#font_size" do
16
+ it "should allow setting font size in DSL style" do
17
+ create_pdf
18
+ @pdf.font_size 20
19
+ @pdf.font_size.should == 20
20
+ end
21
+ end
22
+
15
23
  describe "font style support" do
16
24
  before(:each) { create_pdf }
17
25
 
@@ -115,7 +123,7 @@ describe "Document#page_fonts" do
115
123
  end
116
124
 
117
125
  def page_includes_font?(font)
118
- @pdf.page_fonts.values.map { |e| e.data[:BaseFont] }.include?(font.to_sym)
126
+ @pdf.page.fonts.values.map { |e| e.data[:BaseFont] }.include?(font.to_sym)
119
127
  end
120
128
 
121
129
  def page_should_include_font(font)
@@ -113,7 +113,39 @@ describe "When drawing a curve" do
113
113
  curve = PDF::Inspector::Graphics::Curve.analyze(@pdf.render)
114
114
  curve.coords.should == [100.0, 100.0, 20.0, 90.0, 90.0, 75.0, 50.0, 50.0]
115
115
  end
116
+
116
117
 
118
+ end
119
+
120
+ describe "When drawing a rounded rectangle" do
121
+ before(:each) do
122
+ create_pdf
123
+ @pdf.rounded_rectangle([50, 550], 50, 100, 10)
124
+ curve = PDF::Inspector::Graphics::Curve.analyze(@pdf.render)
125
+ curve_points = []
126
+ curve.coords.each_slice(2) {|p| curve_points << p}
127
+ @original_point = curve_points.shift
128
+ curves = []
129
+ curve_points.each_slice(3) {|c| curves << c}
130
+ line_points = PDF::Inspector::Graphics::Line.analyze(@pdf.render).points
131
+ line_points.shift
132
+ @all_coords = []
133
+ line_points.zip(curves).flatten.each_slice(2) {|p| @all_coords << p }
134
+ @all_coords.unshift @original_point
135
+ end
136
+
137
+ it "should draw a rectangle by connecting lines with rounded bezier curves" do
138
+ @all_coords.should == [[60.0, 550.0],[90.0, 550.0], [95.523, 550.0], [100.0, 545.523], [100.0, 540.0],
139
+ [100.0, 460.0], [100.0, 454.477], [95.523, 450.0], [90.0, 450.0],
140
+ [60.0, 450.0], [54.477, 450.0], [50.0, 454.477], [50.0, 460.0],
141
+ [50.0, 540.0], [50.0, 545.523], [54.477, 550.0], [60.0, 550.0]]
142
+ end
143
+
144
+ it "should start and end with the same point" do
145
+ @original_point.should == @all_coords.last
146
+ end
147
+
148
+
117
149
  end
118
150
 
119
151
  describe "When drawing an ellipse" do
@@ -207,3 +239,200 @@ describe "When using painting shortcuts" do
207
239
  should.raise(NoMethodError)
208
240
  end
209
241
  end
242
+
243
+ describe "When using graphics states" do
244
+ before(:each) { create_pdf }
245
+
246
+ it "should add the right content on save_graphics_state" do
247
+ @pdf.expects(:add_content).with('q')
248
+
249
+ @pdf.save_graphics_state
250
+ end
251
+
252
+ it "should add the right content on restore_graphics_state" do
253
+ @pdf.expects(:add_content).with('Q')
254
+
255
+ @pdf.restore_graphics_state
256
+ end
257
+
258
+ it "should save and restore when save_graphics_state is used with a block" do
259
+ state = sequence "state"
260
+ @pdf.expects(:add_content).with('q').in_sequence(state)
261
+ @pdf.expects(:foo).in_sequence(state)
262
+ @pdf.expects(:add_content).with('Q').in_sequence(state)
263
+
264
+ @pdf.save_graphics_state do
265
+ @pdf.foo
266
+ end
267
+ end
268
+ end
269
+
270
+ describe "When using transformation matrix" do
271
+ before(:each) { create_pdf }
272
+
273
+ # Note: The (approximate) number of significant decimal digits of precision in fractional
274
+ # part is 5 (PDF Reference, Third Edition, p. 706)
275
+
276
+ it "should send the right content on transformation_matrix" do
277
+ @pdf.expects(:add_content).with('1.00000 0.00000 0.12346 -1.00000 5.50000 20.00000 cm')
278
+ @pdf.transformation_matrix 1, 0, 0.123456789, -1.0, 5.5, 20
279
+ end
280
+
281
+ it "should use fixed digits with very small number" do
282
+ values = Array.new(6, 0.000000000001)
283
+ string = Array.new(6, "0.00000").join " "
284
+ @pdf.expects(:add_content).with("#{string} cm")
285
+ @pdf.transformation_matrix *values
286
+ end
287
+
288
+ it "should be received by the inspector" do
289
+ @pdf.transformation_matrix 1, 0, 0, -1, 5.5, 20
290
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(@pdf.render)
291
+ matrices.matrices.should == [[1, 0, 0, -1, 5.5, 20]]
292
+ end
293
+
294
+ it "should save the graphics state inside the given block" do
295
+ values = Array.new(6, 0.000000000001)
296
+ string = Array.new(6, "0.00000").join " "
297
+ process = sequence "process"
298
+
299
+ @pdf.expects(:save_graphics_state).with().in_sequence(process)
300
+ @pdf.expects(:add_content).with("#{string} cm").in_sequence(process)
301
+ @pdf.expects(:do_something).with().in_sequence(process)
302
+ @pdf.expects(:restore_graphics_state).with().in_sequence(process)
303
+ @pdf.transformation_matrix(*values) do
304
+ @pdf.do_something
305
+ end
306
+ end
307
+
308
+ end
309
+
310
+ describe "When using transformations shortcuts" do
311
+ before(:each) do
312
+ create_pdf
313
+ @x, @y = 12, 54.32
314
+ @angle = 12.32
315
+ @cos = Math.cos(@angle * Math::PI / 180)
316
+ @sin = Math.sin(@angle * Math::PI / 180)
317
+ @factor = 0.12
318
+ end
319
+
320
+ describe "#rotate" do
321
+ it "should rotate" do
322
+ @pdf.expects(:transformation_matrix).with(@cos, @sin, -@sin, @cos, 0, 0)
323
+ @pdf.rotate(@angle)
324
+ end
325
+ end
326
+
327
+ describe "#rotate with :origin option" do
328
+ it "should rotate around the origin" do
329
+ x_prime = @x * @cos - @y * @sin
330
+ y_prime = @x * @sin + @y * @cos
331
+
332
+ @pdf.rotate(@angle, :origin => [@x, @y]) { @pdf.text('hello world') }
333
+
334
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(@pdf.render)
335
+ matrices.matrices[0].should == [1, 0, 0, 1,
336
+ reduce_precision(@x - x_prime),
337
+ reduce_precision(@y - y_prime)]
338
+ matrices.matrices[1].should == [reduce_precision(@cos),
339
+ reduce_precision(@sin),
340
+ reduce_precision(-@sin),
341
+ reduce_precision(@cos), 0, 0]
342
+ end
343
+
344
+ it "should rotate around the origin in a document with a margin" do
345
+ @pdf = Prawn::Document.new
346
+
347
+ @pdf.rotate(@angle, :origin => [@x, @y]) { @pdf.text('hello world') }
348
+
349
+ x = @x + @pdf.bounds.absolute_left
350
+ y = @y + @pdf.bounds.absolute_bottom
351
+ x_prime = x * @cos - y * @sin
352
+ y_prime = x * @sin + y * @cos
353
+
354
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(@pdf.render)
355
+ matrices.matrices[0].should == [1, 0, 0, 1,
356
+ reduce_precision(x - x_prime),
357
+ reduce_precision(y - y_prime)]
358
+ matrices.matrices[1].should == [reduce_precision(@cos),
359
+ reduce_precision(@sin),
360
+ reduce_precision(-@sin),
361
+ reduce_precision(@cos), 0, 0]
362
+ end
363
+
364
+ it "should raise BlockRequired if no block is given" do
365
+ lambda {
366
+ @pdf.rotate(@angle, :origin => [@x, @y])
367
+ }.should.raise(Prawn::Errors::BlockRequired)
368
+ end
369
+
370
+ def reduce_precision(float)
371
+ ("%.5f" % float).to_f
372
+ end
373
+ end
374
+
375
+ describe "#translate" do
376
+ it "should translate" do
377
+ x, y = 12, 54.32
378
+ @pdf.expects(:transformation_matrix).with(1, 0, 0, 1, x, y)
379
+ @pdf.translate(x, y)
380
+ end
381
+ end
382
+
383
+ describe "#scale" do
384
+ it "should scale" do
385
+ @pdf.expects(:transformation_matrix).with(@factor, 0, 0, @factor, 0, 0)
386
+ @pdf.scale(@factor)
387
+ end
388
+ end
389
+
390
+ describe "#scale with :origin option" do
391
+ it "should scale from the origin" do
392
+ x_prime = @factor * @x
393
+ y_prime = @factor * @y
394
+
395
+ @pdf.scale(@factor, :origin => [@x, @y]) { @pdf.text('hello world') }
396
+
397
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(@pdf.render)
398
+ matrices.matrices[0].should == [1, 0, 0, 1,
399
+ reduce_precision(@x - x_prime),
400
+ reduce_precision(@y - y_prime)]
401
+ matrices.matrices[1].should == [@factor, 0, 0, @factor, 0, 0]
402
+ end
403
+
404
+ it "should scale from the origin in a document with a margin" do
405
+ @pdf = Prawn::Document.new
406
+ x = @x + @pdf.bounds.absolute_left
407
+ y = @y + @pdf.bounds.absolute_bottom
408
+ x_prime = @factor * x
409
+ y_prime = @factor * y
410
+
411
+ @pdf.scale(@factor, :origin => [@x, @y]) { @pdf.text('hello world') }
412
+
413
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(@pdf.render)
414
+ matrices.matrices[0].should == [1, 0, 0, 1,
415
+ reduce_precision(x - x_prime),
416
+ reduce_precision(y - y_prime)]
417
+ matrices.matrices[1].should == [@factor, 0, 0, @factor, 0, 0]
418
+ end
419
+
420
+ it "should raise BlockRequired if no block is given" do
421
+ lambda {
422
+ @pdf.scale(@factor, :origin => [@x, @y])
423
+ }.should.raise(Prawn::Errors::BlockRequired)
424
+ end
425
+
426
+ def reduce_precision(float)
427
+ ("%.5f" % float).to_f
428
+ end
429
+ end
430
+
431
+ # describe "skew" do
432
+ # it "should skew" do
433
+ # a, b = 30, 50.2
434
+ # @pdf.expects(:transformation_matrix).with(1, Math.tan(a * Math::PI / 180), Math.tan(b * Math::PI / 180), 1, 0, 0)
435
+ # @pdf.skew(a, b)
436
+ # end
437
+ # end
438
+ end
@@ -4,11 +4,11 @@ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
4
4
 
5
5
  describe "Prawn::ObjectStore" do
6
6
  before(:each) do
7
- @store = Prawn::ObjectStore.new
7
+ @store = Prawn::Core::ObjectStore.new
8
8
  end
9
9
 
10
10
  it "should create required roots by default, including info passed to new" do
11
- store = Prawn::ObjectStore.new(:Test => 3)
11
+ store = Prawn::Core::ObjectStore.new(:Test => 3)
12
12
  store.size.should == 3 # 3 default roots
13
13
  store.info.data[:Test].should == 3
14
14
  store.pages.data[:Count].should == 0
@@ -43,7 +43,7 @@ end
43
43
 
44
44
  describe "Prawn::ObjectStore#compact" do
45
45
  it "should do nothing to an ObjectStore with all live refs" do
46
- store = Prawn::ObjectStore.new
46
+ store = Prawn::Core::ObjectStore.new
47
47
  store.info.data[:Blah] = store.ref(:some => "structure")
48
48
  old_size = store.size
49
49
  store.compact
@@ -52,7 +52,7 @@ describe "Prawn::ObjectStore#compact" do
52
52
  end
53
53
 
54
54
  it "should remove dead objects, renumbering live objects from 1" do
55
- store = Prawn::ObjectStore.new
55
+ store = Prawn::Core::ObjectStore.new
56
56
  store.ref(:some => "structure")
57
57
  old_size = store.size
58
58
  store.compact
@@ -62,7 +62,7 @@ describe "Prawn::ObjectStore#compact" do
62
62
  end
63
63
 
64
64
  it "should detect and remove dead objects that were once live" do
65
- store = Prawn::ObjectStore.new
65
+ store = Prawn::Core::ObjectStore.new
66
66
  store.info.data[:Blah] = store.ref(:some => "structure")
67
67
  store.info.data[:Blah] = :overwritten
68
68
  old_size = store.size
@@ -0,0 +1,229 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
2
+
3
+ describe "Outline" do
4
+ before(:each) do
5
+ @pdf = Prawn::Document.new() do
6
+ text "Page 1. This is the first Chapter. "
7
+ start_new_page
8
+ text "Page 2. More in the first Chapter. "
9
+ start_new_page
10
+ define_outline do
11
+ section 'Chapter 1', :page => 1, :closed => true do
12
+ page 1, :title => 'Page 1'
13
+ page 2, :title => 'Page 2'
14
+ end
15
+ end
16
+ end
17
+ end
18
+ describe "#generate_outline" do
19
+ before(:each) do
20
+ render_and_find_objects
21
+ end
22
+
23
+ it "should create a root outline dictionary item" do
24
+ assert_not_nil @outline_root
25
+ end
26
+
27
+ it "should set the first and last top items of the root outline dictionary item" do
28
+ referenced_object(@outline_root[:First]).should == @section_1
29
+ referenced_object(@outline_root[:Last]).should == @section_1
30
+ end
31
+
32
+ describe "#create_outline_item" do
33
+ it "should create outline items for each section and page" do
34
+ [@section_1, @page_1, @page_2].each {|item| assert_not_nil item}
35
+ end
36
+ end
37
+
38
+ describe "#set_relations, #set_variables_for_block, and #reset_parent" do
39
+ it "should link sibling items" do
40
+ referenced_object(@page_1[:Next]).should == @page_2
41
+ referenced_object(@page_2[:Prev]).should == @page_1
42
+ end
43
+
44
+ it "should link child items to parent item" do
45
+ [@page_1, @page_2].each {|page| referenced_object(page[:Parent]).should == @section_1 }
46
+ end
47
+
48
+ it "should set the first and last child items for parent item" do
49
+ referenced_object(@section_1[:First]).should == @page_1
50
+ referenced_object(@section_1[:Last]).should == @page_2
51
+ end
52
+ end
53
+
54
+ describe "#increase_count" do
55
+
56
+ it "should add the count of all descendant items" do
57
+ @outline_root[:Count].should == 3
58
+ @section_1[:Count].should.abs == 2
59
+ @page_1[:Count].should == 0
60
+ @page_2[:Count].should == 0
61
+ end
62
+
63
+ end
64
+
65
+ describe "closed option" do
66
+
67
+ it "should set the item's integer count to negative" do
68
+ @section_1[:Count].should == -2
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ describe "#outline.add_section" do
76
+ before(:each) do
77
+ @pdf.start_new_page
78
+ @pdf.text "Page 3. An added section "
79
+ @pdf.outline.add_section do
80
+ section 'Added Section', :page => 3 do
81
+ page 3, :title => 'Page 3'
82
+ end
83
+ end
84
+ render_and_find_objects
85
+ end
86
+
87
+ it "should add new outline items to document" do
88
+ [@section_2, @page_3].each { |item| assert_not_nil item}
89
+ end
90
+
91
+ it "should reset the last items for root outline dictionary" do
92
+ referenced_object(@outline_root[:First]).should == @section_1
93
+ referenced_object(@outline_root[:Last]).should == @section_2
94
+ end
95
+
96
+ it "should reset the next relation for the previous last top level item" do
97
+ referenced_object(@section_1[:Next]).should == @section_2
98
+ end
99
+
100
+ it "should set the previous relation of the addded to section" do
101
+ referenced_object(@section_2[:Prev]).should == @section_1
102
+ end
103
+
104
+ it "should increase the count of root outline dictionary" do
105
+ @outline_root[:Count].should == 5
106
+ end
107
+
108
+ end
109
+
110
+ describe "#outline.insert_section_after" do
111
+ describe "inserting in the middle of another section" do
112
+ before(:each) do
113
+ @pdf.go_to_page 1
114
+ @pdf.start_new_page
115
+ @pdf.text "Inserted Page"
116
+ @pdf.outline.insert_section_after 'Page 1' do
117
+ page page_number, :title => "Inserted Page"
118
+ end
119
+ render_and_find_objects
120
+ end
121
+
122
+ it "should insert new outline items to document" do
123
+ assert_not_nil @inserted_page
124
+ end
125
+
126
+ it "should adjust the count of all ancestors" do
127
+ @outline_root[:Count].should == 4
128
+ @section_1[:Count].should.abs == 3
129
+ end
130
+
131
+ describe "#adjust_relations" do
132
+
133
+ it "should reset the sibling relations of adjoining items to inserted item" do
134
+ referenced_object(@page_1[:Next]).should == @inserted_page
135
+ referenced_object(@page_2[:Prev]).should == @inserted_page
136
+ end
137
+
138
+ it "should set the sibling relation of added item to adjoining items" do
139
+ referenced_object(@inserted_page[:Next]).should == @page_2
140
+ referenced_object(@inserted_page[:Prev]).should == @page_1
141
+ end
142
+
143
+ it "should not affect the first and last relations of parent item" do
144
+ referenced_object(@section_1[:First]).should == @page_1
145
+ referenced_object(@section_1[:Last]).should == @page_2
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ describe "inserting at the end of another section" do
153
+ before(:each) do
154
+ @pdf.go_to_page 2
155
+ @pdf.start_new_page
156
+ @pdf.text "Inserted Page"
157
+ @pdf.outline.insert_section_after 'Page 2' do
158
+ page page_number, :title => "Inserted Page"
159
+ end
160
+ render_and_find_objects
161
+ end
162
+
163
+ describe "#adjust_relations" do
164
+
165
+ it "should reset the sibling relations of adjoining item to inserted item" do
166
+ referenced_object(@page_2[:Next]).should == @inserted_page
167
+ end
168
+
169
+ it "should set the sibling relation of added item to adjoining items" do
170
+ assert_nil referenced_object(@inserted_page[:Next])
171
+ referenced_object(@inserted_page[:Prev]).should == @page_2
172
+ end
173
+
174
+ it "should adjust the last relation of parent item" do
175
+ referenced_object(@section_1[:Last]).should == @inserted_page
176
+ end
177
+
178
+ end
179
+ end
180
+
181
+ it "should require an existing title" do
182
+ assert_raise Prawn::Errors::UnknownOutlineTitle do
183
+ @pdf.go_to_page 1
184
+ @pdf.start_new_page
185
+ @pdf.text "Inserted Page"
186
+ @pdf.outline.insert_section_after 'Wrong page' do
187
+ page page_number, :title => "Inserted Page"
188
+ end
189
+ render_and_find_objects
190
+ end
191
+ end
192
+
193
+ end
194
+
195
+ describe "#page" do
196
+ it "should require a title option to be set" do
197
+ assert_raise Prawn::Errors::RequiredOption do
198
+ @pdf = Prawn::Document.new() do
199
+ text "Page 1. This is the first Chapter. "
200
+ define_outline do
201
+ page 1, :title => nil
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def render_and_find_objects
210
+ output = StringIO.new(@pdf.render, 'r+')
211
+ @hash = PDF::Hash.new(output)
212
+ @outline_root = @hash.values.find {|obj| obj.is_a?(Hash) && obj[:Type] == :Outlines}
213
+ @pages = @hash.values.find {|obj| obj.is_a?(Hash) && obj[:Type] == :Pages}[:Kids]
214
+ @section_1 = find_by_title('Chapter 1')
215
+ @page_1 = find_by_title('Page 1')
216
+ @page_2 = find_by_title('Page 2')
217
+ @section_2 = find_by_title('Added Section')
218
+ @page_3 = find_by_title('Page 3')
219
+ @inserted_page = find_by_title('Inserted Page')
220
+ end
221
+
222
+ def find_by_title(title)
223
+ @hash.values.find {|obj| obj.is_a?(Hash) && obj[:Title] == title}
224
+ end
225
+
226
+ def referenced_object(reference)
227
+ @hash[reference]
228
+ end
229
+