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
@@ -0,0 +1,66 @@
1
+ %PDF-1.3
2
+ %����
3
+ 1 0 obj
4
+ << /Creator (Prawn)
5
+ /Producer (Prawn)
6
+ >>
7
+ endobj
8
+ 2 0 obj
9
+ << /Type /Pages
10
+ /Count 1
11
+ /Kids [5 0 R]
12
+ >>
13
+ endobj
14
+ 3 0 obj
15
+ << /Type /Catalog
16
+ /Pages 2 0 R
17
+ >>
18
+ endobj
19
+ 4 0 obj
20
+ << /Length 380
21
+ >>
22
+ stream
23
+ /DeviceRGB cs
24
+ 0.000 0.000 0.000 scn
25
+ /DeviceRGB CS
26
+ 0.000 0.000 0.000 SCN
27
+ q
28
+ 136.000 136.000 m
29
+ 96.000 126.000 96.000 126.000 86.000 86.000 c
30
+ S
31
+ 246.000 236.000 m
32
+ 246.000 241.523 241.523 246.000 236.000 246.000 c
33
+ 230.477 246.000 226.000 241.523 226.000 236.000 c
34
+ 226.000 230.477 230.477 226.000 236.000 226.000 c
35
+ 241.523 226.000 246.000 230.477 246.000 236.000 c
36
+ 236.000 236.000 m
37
+ f
38
+ Q
39
+
40
+ endstream
41
+ endobj
42
+ 5 0 obj
43
+ << /Type /Page
44
+ /Parent 2 0 R
45
+ /MediaBox [0 0 612.0 792.0]
46
+ /Contents 4 0 R
47
+ /Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
48
+ >>
49
+ >>
50
+ endobj
51
+ xref
52
+ 0 6
53
+ 0000000000 65535 f
54
+ 0000000015 00000 n
55
+ 0000000071 00000 n
56
+ 0000000128 00000 n
57
+ 0000000177 00000 n
58
+ 0000000608 00000 n
59
+ trailer
60
+ << /Size 6
61
+ /Root 3 0 R
62
+ /Info 1 0 R
63
+ >>
64
+ startxref
65
+ 762
66
+ %%EOF
@@ -3,6 +3,14 @@ require "tempfile"
3
3
 
4
4
  require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
5
5
 
6
+ describe "Prawn::Document.new" do
7
+ it "should not modify its argument" do
8
+ options = {:page_layout => :landscape}
9
+ Prawn::Document.new(options)
10
+ options.should == {:page_layout => :landscape}
11
+ end
12
+ end
13
+
6
14
  describe "The cursor" do
7
15
  it "should equal pdf.y - bounds.absolute_bottom" do
8
16
  pdf = Prawn::Document.new
@@ -101,6 +109,31 @@ describe "When beginning each new page" do
101
109
 
102
110
  end
103
111
 
112
+ describe "Prawn::Document#float" do
113
+ it "should restore the original y-position" do
114
+ create_pdf
115
+ orig_y = @pdf.y
116
+ @pdf.float { @pdf.text "Foo" }
117
+ @pdf.y.should == orig_y
118
+ end
119
+
120
+ it "should teleport across pages if necessary" do
121
+ create_pdf
122
+
123
+ @pdf.float do
124
+ @pdf.text "Foo"
125
+ @pdf.start_new_page
126
+ @pdf.text "Bar"
127
+ end
128
+ @pdf.text "Baz"
129
+
130
+ pages = PDF::Inspector::Page.analyze(@pdf.render).pages
131
+ pages.size.should == 2
132
+ pages[0][:strings].should == ["Foo", "Baz"]
133
+ pages[1][:strings].should == ["Bar"]
134
+ end
135
+ end
136
+
104
137
  describe "The page_number method" do
105
138
  it "should be 1 for a new document" do
106
139
  pdf = Prawn::Document.new
@@ -250,6 +283,15 @@ describe "When reopening pages" do
250
283
  pages[1][:strings].should == ["New page 2"]
251
284
  pages[2][:strings].should == ["Old page 2"]
252
285
  end
286
+
287
+ it "should update the bounding box to the new page's margin box" do
288
+ Prawn::Document.new do
289
+ start_new_page :layout => :landscape
290
+ lsize = [bounds.width, bounds.height]
291
+ go_to_page 1
292
+ [bounds.width, bounds.height].should == lsize.reverse
293
+ end
294
+ end
253
295
  end
254
296
 
255
297
  describe "When setting page size" do
@@ -292,7 +334,7 @@ describe "When setting page layout" do
292
334
  pages.first[:size].should == Prawn::Document::PageGeometry::SIZES["A4"].reverse
293
335
  end
294
336
 
295
- it "should retain page layout by default when starting a new page" do
337
+ it "should retain page layout by default when starting a new page" do
296
338
  @pdf = Prawn::Document.new(:page_layout => :landscape)
297
339
  @pdf.start_new_page(:trace => true)
298
340
  pages = PDF::Inspector::Page.analyze(@pdf.render).pages
@@ -300,6 +342,13 @@ describe "When setting page layout" do
300
342
  page[:size].should == Prawn::Document::PageGeometry::SIZES["LETTER"].reverse
301
343
  end
302
344
  end
345
+
346
+ it "should swap the bounds when starting a new page with different layout" do
347
+ @pdf = Prawn::Document.new
348
+ size = [@pdf.bounds.width, @pdf.bounds.height]
349
+ @pdf.start_new_page(:layout => :landscape)
350
+ [@pdf.bounds.width, @pdf.bounds.height].should == size.reverse
351
+ end
303
352
  end
304
353
 
305
354
  describe "The mask() feature" do
@@ -529,13 +578,19 @@ describe "The number_pages method" do
529
578
  @pdf.number_pages "test, <total>", {:page_filter => :all}
530
579
  end
531
580
 
532
- it "must print the page if the page number matches" do
581
+ it "must print each page if given the :all page_filter" do
533
582
  10.times { @pdf.start_new_page }
534
- @pdf.expects(:text_box).at_least_once
583
+ @pdf.expects(:text_box).times(10)
535
584
  @pdf.number_pages "test", {:page_filter => :all}
536
585
  end
586
+
587
+ it "must print each page if no :page_filter is specified" do
588
+ 10.times { @pdf.start_new_page }
589
+ @pdf.expects(:text_box).times(10)
590
+ @pdf.number_pages "test"
591
+ end
537
592
 
538
- it "must not print the page number without a filter" do
593
+ it "must not print the page number if given a nil filter" do
539
594
  10.times { @pdf.start_new_page }
540
595
  @pdf.expects(:text_box).never
541
596
  @pdf.number_pages "test", {:page_filter => nil}
@@ -12,12 +12,10 @@
12
12
  class ParameterChecker < Mocha::ParametersMatcher
13
13
  def initialize(&matching_block)
14
14
  @matching_block = matching_block
15
- @run_matching_block = false
16
15
  end
17
16
 
18
17
  def match?(actual_parameters = [])
19
- @matching_block.call(*actual_parameters) unless @run_matching_block
20
- @run_matching_block = true
18
+ @matching_block.call(*actual_parameters)
21
19
 
22
20
  true # always succeed
23
21
  end
@@ -20,6 +20,15 @@ describe "#width_of" do
20
20
  @pdf.width_of("hello world").should == original_width + 11 * 7
21
21
  end
22
22
  end
23
+
24
+ it "should exclude newlines" do
25
+ create_pdf
26
+ # Use a TTF font that has a non-zero width for \n
27
+ @pdf.font("#{Prawn::BASEDIR}/data/fonts/gkai00mp.ttf")
28
+
29
+ @pdf.width_of("\nhello world\n").should ==
30
+ @pdf.width_of("hello world")
31
+ end
23
32
  end
24
33
 
25
34
  describe "#font_size" do
@@ -353,3 +362,17 @@ describe "DFont fonts" do
353
362
  assert_equal f2.object_id, @pdf.find_font(@file, :font => "ActionMan-Bold").object_id
354
363
  end
355
364
  end
365
+
366
+ describe "#character_count(text)" do
367
+ it "should work on TTF fonts" do
368
+ create_pdf
369
+ @pdf.font("#{Prawn::BASEDIR}/data/fonts/gkai00mp.ttf")
370
+ @pdf.font.character_count("こんにちは世界").should == 7
371
+ @pdf.font.character_count("Hello, world!").should == 13
372
+ end
373
+
374
+ it "should work on AFM fonts" do
375
+ create_pdf
376
+ @pdf.font.character_count("Hello, world!").should == 13
377
+ end
378
+ end
@@ -75,6 +75,46 @@ describe "Text::Formatted::Box wrapping" do
75
75
  text_box.render
76
76
  text_box.text.should == "Hello World\n2"
77
77
  end
78
+
79
+ it "should properly handle empty slices using default encoding" do
80
+ texts = [{ :text => "Noua Delineatio Geographica generalis | Apostolicarum peregrinationum | S FRANCISCI XAUERII | Indiarum & Iaponiæ Apostoli", :font => 'Courier', :size => 10 }]
81
+ text_box = Prawn::Text::Formatted::Box.new(texts, :document => @pdf, :width => @pdf.width_of("Noua Delineatio Geographica gen"))
82
+ assert_nothing_raised do
83
+ text_box.render
84
+ end
85
+ text_box.text.should == "Noua Delineatio Geographica\ngeneralis | Apostolicarum\nperegrinationum | S FRANCISCI\nXAUERII | Indiarum & Iaponi\346\nApostoli"
86
+ end
87
+
88
+ describe "Unicode" do
89
+ before do
90
+ if RUBY_VERSION < '1.9'
91
+ @reset_value = $KCODE
92
+ $KCODE='u'
93
+ else
94
+ @reset_value = [Encoding.default_external, Encoding.default_internal]
95
+ Encoding.default_external = Encoding::UTF_8
96
+ Encoding.default_internal = Encoding::UTF_8
97
+ end
98
+ end
99
+
100
+ after do
101
+ if RUBY_VERSION < '1.9'
102
+ $KCODE=@reset_value
103
+ else
104
+ Encoding.default_external = @reset_value[0]
105
+ Encoding.default_internal = @reset_value[1]
106
+ end
107
+ end
108
+
109
+ it "should properly handle empty slices using Unicode encoding" do
110
+ texts = [{ :text => "Noua Delineatio Geographica generalis | Apostolicarum peregrinationum | S FRANCISCI XAUERII | Indiarum & Iaponiæ Apostoli", :font => 'Courier', :size => 10 }]
111
+ text_box = Prawn::Text::Formatted::Box.new(texts, :document => @pdf, :width => @pdf.width_of("Noua Delineatio Geographica gen"))
112
+ assert_nothing_raised do
113
+ text_box.render
114
+ end
115
+ text_box.text.should == "Noua Delineatio Geographica\ngeneralis | Apostolicarum\nperegrinationum | S FRANCISCI\nXAUERII | Indiarum & Iaponi\346\nApostoli"
116
+ end
117
+ end
78
118
  end
79
119
 
80
120
  describe "Text::Formatted::Box with :fallback_fonts option that includes" +
@@ -91,6 +91,13 @@ describe "the image() function" do
91
91
  Set.new(hash[pages[0]][:Resources][:XObject].keys).should ==
92
92
  Set.new([:I1, :I2])
93
93
  end
94
+
95
+ it "should not start a new page just for a stretchy bounding box" do
96
+ @pdf.expects(:start_new_page).times(0)
97
+ @pdf.bounding_box([0, @pdf.cursor], :width => @pdf.bounds.width) do
98
+ @pdf.image @filename
99
+ end
100
+ end
94
101
 
95
102
  describe ":fit option" do
96
103
  it "should fit inside the defined constraints" do
@@ -16,20 +16,20 @@ describe "Prawn::ObjectStore" do
16
16
  end
17
17
 
18
18
  it "should import objects from an existing PDF" do
19
- filename = "#{Prawn::BASEDIR}/reference_pdfs/curves.pdf"
19
+ filename = "#{Prawn::BASEDIR}/spec/data/curves.pdf"
20
20
  store = Prawn::Core::ObjectStore.new(:template => filename)
21
21
  store.size.should == 5
22
22
  end
23
23
 
24
24
  it "should point to existing roots when importing objects from an existing PDF" do
25
- filename = "#{Prawn::BASEDIR}/reference_pdfs/curves.pdf"
25
+ filename = "#{Prawn::BASEDIR}/spec/data/curves.pdf"
26
26
  store = Prawn::Core::ObjectStore.new(:template => filename)
27
27
  store.info.class.should == Prawn::Core::Reference
28
28
  store.root.class.should == Prawn::Core::Reference
29
29
  end
30
30
 
31
31
  it "should initialize with pages when importing objects from an existing PDF" do
32
- filename = "#{Prawn::BASEDIR}/reference_pdfs/curves.pdf"
32
+ filename = "#{Prawn::BASEDIR}/spec/data/curves.pdf"
33
33
  store = Prawn::Core::ObjectStore.new(:template => filename)
34
34
  store.pages.data[:Count].should == 1
35
35
  end
@@ -112,6 +112,39 @@ describe "Prawn::Document#transaction" do
112
112
  end
113
113
 
114
114
  end
115
+
116
+ it "should restore bounds on rollback" do
117
+ Prawn::Document.new(:page_layout => :landscape) do
118
+ size = [bounds.width, bounds.height]
119
+ transaction do
120
+ start_new_page :layout => :portrait
121
+ rollback
122
+ end
123
+ [bounds.width, bounds.height].should == size
124
+ end
125
+ end
126
+
127
+ it "should set new bounding box on start_new_page with different layout" do
128
+ Prawn::Document.new(:page_layout => :landscape) do
129
+ size = [bounds.width, bounds.height]
130
+ transaction do
131
+ start_new_page
132
+ rollback
133
+ end
134
+
135
+ start_new_page :layout => :portrait
136
+ [bounds.width, bounds.height].should == size.reverse
137
+ end
138
+ end
139
+
140
+ it "should work with dests" do
141
+ Prawn::Document.new do |pdf|
142
+ pdf.add_dest("dest", pdf.dest_fit_horizontally(pdf.cursor,
143
+ pdf.page.dictionary))
144
+ pdf.text("Hello world")
145
+ lambda { pdf.transaction{} }.should.not.raise
146
+ end
147
+ end
115
148
 
116
149
  describe "with a stamp dictionary present" do
117
150
 
@@ -219,6 +219,13 @@ describe "Prawn::Table" do
219
219
  table.width.should == 300
220
220
  end
221
221
 
222
+ it "should accept Numeric for column_widths" do
223
+ table = Prawn::Table.new([%w[ a b c ], %w[d e f]], @pdf) do |t|
224
+ t.column_widths = 50
225
+ end
226
+ table.width.should == 150
227
+ end
228
+
222
229
  it "should calculate unspecified column widths as "+
223
230
  "(max(string_width) + 2*horizontal_padding)" do
224
231
  hpad, fs = 3, 12
@@ -485,6 +492,37 @@ describe "Prawn::Table" do
485
492
  output.pages[0][:strings].should.be.empty
486
493
  output.pages[1][:strings].should == ["Header", "Body"]
487
494
  end
495
+
496
+ it "should draw background before borders, but only within pages" do
497
+ seq = sequence("drawing_order")
498
+
499
+ @pdf = Prawn::Document.new
500
+
501
+ # give enough room for only the first row
502
+ @pdf.y = @pdf.bounds.absolute_bottom + 30
503
+ t = @pdf.make_table([["A", "B"],
504
+ ["C", "D"]],
505
+ :cell_style => {:background_color => 'ff0000'})
506
+
507
+ ca = t.cells[0, 0]
508
+ cb = t.cells[0, 1]
509
+ cc = t.cells[1, 0]
510
+ cd = t.cells[1, 1]
511
+
512
+ # All backgrounds should draw before any borders on page 1...
513
+ ca.expects(:draw_background).in_sequence(seq)
514
+ cb.expects(:draw_background).in_sequence(seq)
515
+ ca.expects(:draw_borders).in_sequence(seq)
516
+ cb.expects(:draw_borders).in_sequence(seq)
517
+ # ...and page 2
518
+ @pdf.expects(:start_new_page).in_sequence(seq)
519
+ cc.expects(:draw_background).in_sequence(seq)
520
+ cd.expects(:draw_background).in_sequence(seq)
521
+ cc.expects(:draw_borders).in_sequence(seq)
522
+ cd.expects(:draw_borders).in_sequence(seq)
523
+
524
+ t.draw
525
+ end
488
526
  end
489
527
 
490
528
  describe "#style" do
@@ -581,6 +619,27 @@ describe "Prawn::Table" do
581
619
  end
582
620
  end
583
621
 
622
+ it "should draw all backgrounds before any borders" do
623
+ # lest backgrounds overlap borders:
624
+ # https://github.com/sandal/prawn/pull/226
625
+
626
+ seq = sequence("drawing_order")
627
+
628
+ t = @pdf.make_table([["A", "B"]],
629
+ :cell_style => {:background_color => 'ff0000'})
630
+ ca = t.cells[0, 0]
631
+ cb = t.cells[0, 1]
632
+
633
+ # XXX Not a perfectly general test, because it would still be acceptable
634
+ # if we drew B then A
635
+ ca.expects(:draw_background).in_sequence(seq)
636
+ cb.expects(:draw_background).in_sequence(seq)
637
+ ca.expects(:draw_borders).in_sequence(seq)
638
+ cb.expects(:draw_borders).in_sequence(seq)
639
+
640
+ t.draw
641
+ end
642
+
584
643
  it "should allow multiple inkings of the same table" do
585
644
  pdf = Prawn::Document.new
586
645
  t = Prawn::Table.new([["foo"]], pdf)
@@ -595,6 +654,22 @@ describe "Prawn::Table" do
595
654
  pdf.move_cursor_to(400)
596
655
  t.draw
597
656
  end
657
+
658
+ describe "in stretchy bounding boxes" do
659
+ it "should draw all cells on a row at the same y-position" do
660
+ pdf = Prawn::Document.new
661
+
662
+ text_y = pdf.y.to_i - 5 # text starts 5pt below current y pos (padding)
663
+
664
+ pdf.bounding_box([0, pdf.cursor], :width => pdf.bounds.width) do
665
+ pdf.expects(:draw_text!).checking { |text, options|
666
+ pdf.bounds.absolute_top.should == text_y
667
+ }.times(3)
668
+
669
+ pdf.table([%w[a b c]])
670
+ end
671
+ end
672
+ end
598
673
  end
599
674
 
600
675
  describe "headers" do