prawn 0.1.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 (120) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +30 -0
  4. data/Rakefile +83 -0
  5. data/data/fonts/Activa.ttf +0 -0
  6. data/data/fonts/Chalkboard.ttf +0 -0
  7. data/data/fonts/Courier-Bold.afm +342 -0
  8. data/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/data/fonts/Courier-Oblique.afm +342 -0
  10. data/data/fonts/Courier.afm +342 -0
  11. data/data/fonts/DejaVuSans.ttf +0 -0
  12. data/data/fonts/Dustismo_Roman.ttf +0 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/data/fonts/comicsans.ttf +0 -0
  25. data/data/fonts/gkai00mp.ttf +0 -0
  26. data/data/images/dice.png +0 -0
  27. data/data/images/pigs.jpg +0 -0
  28. data/data/images/ruport.png +0 -0
  29. data/data/images/ruport_data.dat +0 -0
  30. data/data/images/ruport_transparent.png +0 -0
  31. data/data/images/stef.jpg +0 -0
  32. data/data/shift_jis_text.txt +1 -0
  33. data/examples/addressbook.csv +6 -0
  34. data/examples/alignment.rb +16 -0
  35. data/examples/bounding_boxes.pdf +62 -0
  36. data/examples/bounding_boxes.rb +30 -0
  37. data/examples/canvas.pdf +81 -0
  38. data/examples/canvas.rb +12 -0
  39. data/examples/cell.rb +27 -0
  40. data/examples/currency.csv +1834 -0
  41. data/examples/curves.rb +10 -0
  42. data/examples/fancy_table.rb +48 -0
  43. data/examples/font_size.rb +19 -0
  44. data/examples/hexagon.rb +14 -0
  45. data/examples/image.pdf +0 -0
  46. data/examples/image.rb +23 -0
  47. data/examples/image2.rb +13 -0
  48. data/examples/inline_styles.pdf +117 -0
  49. data/examples/kerning.rb +27 -0
  50. data/examples/line.rb +31 -0
  51. data/examples/multi_page_layout.rb +14 -0
  52. data/examples/on_page_start.rb +17 -0
  53. data/examples/page_geometry.rb +28 -0
  54. data/examples/polygons.rb +16 -0
  55. data/examples/ruport_formatter.rb +47 -0
  56. data/examples/ruport_helpers.rb +17 -0
  57. data/examples/russian_boxes.rb +34 -0
  58. data/examples/simple_text.rb +15 -0
  59. data/examples/simple_text_ttf.rb +16 -0
  60. data/examples/sjis.rb +19 -0
  61. data/examples/table.rb +45 -0
  62. data/examples/table_bench.rb +92 -0
  63. data/examples/text_flow.rb +65 -0
  64. data/examples/utf8.rb +12 -0
  65. data/lib/prawn.rb +33 -0
  66. data/lib/prawn/compatibility.rb +33 -0
  67. data/lib/prawn/document.rb +334 -0
  68. data/lib/prawn/document/bounding_box.rb +253 -0
  69. data/lib/prawn/document/page_geometry.rb +78 -0
  70. data/lib/prawn/document/table.rb +253 -0
  71. data/lib/prawn/document/text.rb +346 -0
  72. data/lib/prawn/errors.rb +33 -0
  73. data/lib/prawn/font.rb +5 -0
  74. data/lib/prawn/font/cmap.rb +59 -0
  75. data/lib/prawn/font/metrics.rb +414 -0
  76. data/lib/prawn/font/wrapping.rb +45 -0
  77. data/lib/prawn/graphics.rb +285 -0
  78. data/lib/prawn/graphics/cell.rb +226 -0
  79. data/lib/prawn/images.rb +241 -0
  80. data/lib/prawn/images/jpg.rb +43 -0
  81. data/lib/prawn/images/png.rb +178 -0
  82. data/lib/prawn/pdf_object.rb +64 -0
  83. data/lib/prawn/reference.rb +47 -0
  84. data/spec/bounding_box_spec.rb +120 -0
  85. data/spec/box_calculation_spec.rb +17 -0
  86. data/spec/document_spec.rb +152 -0
  87. data/spec/graphics_spec.rb +250 -0
  88. data/spec/images_spec.rb +42 -0
  89. data/spec/jpg_spec.rb +25 -0
  90. data/spec/metrics_spec.rb +60 -0
  91. data/spec/pdf_object_spec.rb +102 -0
  92. data/spec/png_spec.rb +35 -0
  93. data/spec/reference_spec.rb +29 -0
  94. data/spec/spec_helper.rb +29 -0
  95. data/spec/table_spec.rb +145 -0
  96. data/spec/text_spec.rb +190 -0
  97. data/vendor/font_ttf/ttf.rb +20 -0
  98. data/vendor/font_ttf/ttf/datatypes.rb +189 -0
  99. data/vendor/font_ttf/ttf/encodings.rb +140 -0
  100. data/vendor/font_ttf/ttf/exceptions.rb +28 -0
  101. data/vendor/font_ttf/ttf/file.rb +290 -0
  102. data/vendor/font_ttf/ttf/fontchunk.rb +77 -0
  103. data/vendor/font_ttf/ttf/table/cmap.rb +408 -0
  104. data/vendor/font_ttf/ttf/table/cvt.rb +49 -0
  105. data/vendor/font_ttf/ttf/table/fpgm.rb +48 -0
  106. data/vendor/font_ttf/ttf/table/gasp.rb +88 -0
  107. data/vendor/font_ttf/ttf/table/glyf.rb +452 -0
  108. data/vendor/font_ttf/ttf/table/head.rb +86 -0
  109. data/vendor/font_ttf/ttf/table/hhea.rb +96 -0
  110. data/vendor/font_ttf/ttf/table/hmtx.rb +98 -0
  111. data/vendor/font_ttf/ttf/table/kern.rb +186 -0
  112. data/vendor/font_ttf/ttf/table/loca.rb +75 -0
  113. data/vendor/font_ttf/ttf/table/maxp.rb +81 -0
  114. data/vendor/font_ttf/ttf/table/name.rb +222 -0
  115. data/vendor/font_ttf/ttf/table/os2.rb +172 -0
  116. data/vendor/font_ttf/ttf/table/post.rb +120 -0
  117. data/vendor/font_ttf/ttf/table/prep.rb +27 -0
  118. data/vendor/font_ttf/ttf/table/vhea.rb +45 -0
  119. data/vendor/font_ttf/ttf/table/vmtx.rb +36 -0
  120. metadata +180 -0
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ # Demonstrates some features stolen from Ruport::Formatter::PDF
7
+ Prawn::Document.generate("ruport.pdf") do
8
+ move_down 50
9
+ # TODO: Figure out where to set the y cursor to.
10
+ stroke_horizontal_rule
11
+ text "Hi there"
12
+ pad(50) { text "I'm Padded" }
13
+ text "I'm far away"
14
+ stroke_horizontal_line 50, 100
15
+ stroke_vertical_line_at 300, 50, 250
16
+
17
+ end
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'prawn'
3
+
4
+ class Array
5
+ def combine(arr)
6
+ output = []
7
+ self.each do |i1|
8
+ arr.each do |i2|
9
+ output += [[i1,i2]]
10
+ end
11
+ end
12
+ output
13
+ end
14
+ end
15
+
16
+ def recurse_bounding_box(pdf, max_depth=5, depth=1)
17
+ box = pdf.bounds
18
+ width = (box.width-15)/2
19
+ height = (box.height-15)/2
20
+ left_top_corners = [5, box.right-width-5].combine [box.top-5, height+5]
21
+ left_top_corners.each do |lt|
22
+ pdf.bounding_box(lt, :width=>width, :height=>height) do
23
+ pdf.stroke_rectangle [0,height], width, height
24
+ recurse_bounding_box(pdf, max_depth, depth+1) if depth<max_depth
25
+ end
26
+ end
27
+ end
28
+
29
+
30
+ Prawn::Document.generate("russian_boxes.pdf") do |pdf|
31
+ recurse_bounding_box(pdf)
32
+ end
33
+
34
+
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ Prawn::Document.generate "hello.pdf" do
7
+ fill_color "0000ff"
8
+ text "Hello World", :at => [200,720], :size => 32
9
+ font "Times-Roman"
10
+ fill_color "ff0000"
11
+ text "Overcoming singular font limitation", :at => [5,5]
12
+ start_new_page
13
+ font "Courier"
14
+ text "Goodbye World", :at => [288,50]
15
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ Prawn::Document.generate "hello-ttf.pdf" do
7
+ fill_color "0000ff"
8
+ font "#{Prawn::BASEDIR}/data/fonts/comicsans.ttf"
9
+ text "Hello World", :at => [200,720], :size => 32
10
+
11
+ font "#{Prawn::BASEDIR}/data/fonts/Chalkboard.ttf"
12
+
13
+ pad(20) do
14
+ text "This is chalkboard wrapping " * 20
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+
3
+ # Tests passing non utf-8 data into Prawns text function. Should
4
+ # be transparently converted to utf-8 and rendered as usual.
5
+ #
6
+ # NOTE: only works on ruby1.9 compatible VMs, and requires the current
7
+ # font to include japanese glyphs. On 1.8.x comaptible VMs, an exception
8
+ # will be raised.
9
+
10
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
11
+ require "prawn"
12
+
13
+ datafile = File.join(File.dirname(__FILE__), "..", "data", "shift_jis_text.txt")
14
+ sjis_str = File.open(datafile, "r:shift_jis") { |f| f.gets }
15
+
16
+ Prawn::Document.generate("sjis.pdf") do
17
+ font "#{Prawn::BASEDIR}/data/fonts/gkai00mp.ttf"
18
+ text sjis_str
19
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ Prawn::Document.generate("table.pdf") do
7
+ font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
8
+ table [["ὕαλον ϕαγεῖν", "baaar", "1" ],
9
+ ["This is","a sample", "2" ],
10
+ ["Table", "dont\ncha\nknow?", "3" ],
11
+ [ "It", "Rules", "4" ],
12
+ [ "It", "Rules", "4" ],
13
+ [ "It", "Rules", "4" ],
14
+ [ "It", "Rules", "4" ],
15
+ [ "It", "Rules", "4" ],
16
+ [ "It", "Rules", "4" ],
17
+ [ "It", "Rules", "4" ],
18
+ [ "It", "Rules", "4" ],
19
+ [ "It", "Rules", "4" ],
20
+ [ "It", "Rules\nwith an iron fist", "x" ],
21
+ [ "It", "Rules", "4" ],
22
+ [ "It", "Rules", "4" ],
23
+ [ "It", "Rules", "4" ],
24
+ [ "It", "Rules", "4" ],
25
+ [ "It", "Rules", "4" ],
26
+ [ "It", "Rules", "4" ],
27
+ [ "It", "Rules", "4" ],
28
+ [ "It", "Rules", "4" ],
29
+ [ "It", "Rules", "4" ]],
30
+
31
+ :font_size => 24,
32
+ :horizontal_padding => 10,
33
+ :vertical_padding => 3,
34
+ :border => 2,
35
+ :position => :center,
36
+ :headers => ["Column A","Column B","#"]
37
+ pad(20) do
38
+ text "This should appear in the original font size"
39
+ end
40
+
41
+ table [[ "Wide", "columns", "streeetch"],
42
+ ["are","mighty fine", "streeeeeeeech"]],
43
+ :widths => { 0 => 200, 1 => 250 }, :position => 5
44
+
45
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+
3
+ require "rubygems"
4
+ require "pdf/writer"
5
+ require "pdf/simpletable"
6
+ gem "pdf-wrapper", ">=0.1.2"
7
+ require "pdf/wrapper"
8
+ require "fastercsv"
9
+ require "benchmark"
10
+
11
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
12
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'examples')
13
+ require "prawn"
14
+ require "ruport_formatter"
15
+
16
+ csv_data = FasterCSV.read("#{Prawn::BASEDIR}/examples/currency.csv") *
17
+ (ARGV.first || 1).to_i
18
+
19
+ ####################################
20
+ # PDF::Writer Table Rendering Prep #
21
+ ####################################
22
+ pdf = PDF::Writer.new
23
+ pdf.select_font("Helvetica")
24
+
25
+ table = PDF::SimpleTable.new do |tab|
26
+ tab.column_order.push(*%w(date rate))
27
+
28
+ tab.columns["date"] = PDF::SimpleTable::Column.new("date") { |col|
29
+ col.heading = "Date"
30
+ }
31
+ tab.columns["rate"] = PDF::SimpleTable::Column.new("rate") { |col|
32
+ col.heading = "Rate"
33
+ }
34
+
35
+ tab.orientation = :center
36
+
37
+ data = csv_data.map do |e|
38
+ { "date" => e[0], "rate" => e[1] }
39
+ end
40
+
41
+ tab.data.replace data
42
+ end
43
+
44
+ ##############################
45
+ # Prawn Table Rendering Prep #
46
+ ##############################
47
+ doc = Prawn::Document.new
48
+
49
+ #####################
50
+ # Ruport/Prawn Prep #
51
+ #####################
52
+
53
+ ruport_table = Table(%w[Date Rate], :data => csv_data)
54
+ ruport_doc = Prawn::Document.new
55
+
56
+ ##############################
57
+ # PDF::Wrapper Table Rendering Prep
58
+ ##############################
59
+ wrapper_doc = PDF::Wrapper.new
60
+ wrapper_table = PDF::Wrapper::Table.new do |t|
61
+ t.data = csv_data
62
+ t.table_options :font_size => 6
63
+ end
64
+
65
+ #######################
66
+ # Benchmarking code #
67
+ #######################
68
+
69
+ puts "Processing #{csv_data.length} records"
70
+
71
+ Benchmark.bmbm do |x|
72
+ x.report("PDF Wrapper") do
73
+ wrapper_doc.table( wrapper_table, :width => 100 ) unless wrapper_doc.finished?
74
+ wrapper_doc.render_to_file('currency_pdf_wrapper.pdf')
75
+ end
76
+ x.report("Prawn") do
77
+ doc.table(csv_data, :font_size => 10,
78
+ :vertical_padding => 2,
79
+ :horizontal_padding => 5,
80
+ :position => :center,
81
+ :row_colors => :pdf_writer,
82
+ :headers => ["Date","Rate"])
83
+ doc.render_file('currency_prawn.pdf')
84
+ end
85
+ x.report("Ruport/Prawn") do
86
+ ruport_table.save_as('currency_ruport.pdf', :document => ruport_doc)
87
+ end
88
+ x.report("PDF Writer") do
89
+ table.render_on(pdf)
90
+ pdf.save_as('currency_pdf_writer.pdf')
91
+ end
92
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ content = <<-EOS
7
+ How does
8
+ Prawn deal with
9
+ white
10
+ space
11
+
12
+ and
13
+
14
+ line
15
+ breaks?
16
+ EOS
17
+
18
+ poem = <<-EOS
19
+ GOOD-BYE
20
+
21
+ Good-bye, proud world! I'm going home: Thou art not my friend, and I'm not thine. Long through thy weary crowds I roam; A river-ark on the ocean brine, Long I've been tossed like the driven foam: But now, proud world! I'm going home.
22
+
23
+ Good-bye to Flattery's fawning face; To Grandeur with his wise grimace; To upstart Wealth's averted eye; To supple Office, low and high; To crowded halls, to court and street; To frozen hearts and hasting feet; To those who go, and those who come; Good-bye, proud world! I'm going home.
24
+
25
+ I am going to my own hearth-stone, Bosomed in yon green hills alone,-- secret nook in a pleasant land, Whose groves the frolic fairies planned; Where arches green, the livelong day, Echo the blackbird's roundelay, And vulgar feet have never trod A spot that is sacred to thought and God.
26
+
27
+ O, when I am safe in my sylvan home, I tread on the pride of Greece and Rome; And when I am stretched beneath the pines, Where the evening star so holy shines, I laugh at the lore and the pride of man, At the sophist schools and the learned clan; For what are they all, in their high conceit, When man in the bush with God may meet?
28
+ EOS
29
+
30
+ overflow = "This text should flow gracefully onto the next page, like a stream"+
31
+ " flows elegantly from a mountain lake down into the village below."
32
+
33
+ Prawn::Document.generate("flow.pdf") do |pdf|
34
+
35
+ pdf.font "Times-Roman"
36
+ pdf.stroke_line [pdf.bounds.left, pdf.bounds.top],
37
+ [pdf.bounds.right, pdf.bounds.top]
38
+
39
+ pdf.text content, :size => 10
40
+
41
+ pdf.bounding_box([100,600], :width => 200, :height => 525) do
42
+ pdf.stroke_line [pdf.bounds.left, pdf.bounds.top],
43
+ [pdf.bounds.right, pdf.bounds.top]
44
+ pdf.text poem, :size => 12
45
+ end
46
+
47
+ pdf.bounding_box([325,600], :width => 200, :height => 525) do
48
+ pdf.stroke_line [pdf.bounds.left, pdf.bounds.top],
49
+ [pdf.bounds.right, pdf.bounds.top]
50
+ pdf.text poem.reverse, :size => 12
51
+ end
52
+
53
+ pdf.text overflow * 10, :size => 14
54
+
55
+ pdf.text "Hooray! We've conquered the evil PDF gods", :size => 36
56
+
57
+ pdf.bounding_box([100,425], :width => 300) do
58
+ pdf.stroke_line [pdf.bounds.left, pdf.bounds.top],
59
+ [pdf.bounds.right, pdf.bounds.top]
60
+ pdf.text poem, :size => 12, :spacing => 5
61
+ end
62
+
63
+ pdf.text "And this text automatically goes below the poem", :size => 18
64
+
65
+ end
@@ -0,0 +1,12 @@
1
+ # coding: utf-8
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require "prawn"
5
+
6
+ Prawn::Document.generate("utf8.pdf") do
7
+ font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
8
+ text "ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει." * 20
9
+ end
10
+
11
+
12
+
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn.rb : A library for PDF generation in Ruby
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "prawn/compatibility"
10
+ require "prawn/errors"
11
+ require "prawn/pdf_object"
12
+ require "prawn/graphics"
13
+ require "prawn/images"
14
+ require "prawn/images/jpg"
15
+ require "prawn/images/png"
16
+ require "prawn/document"
17
+ require "prawn/reference"
18
+ require "prawn/font"
19
+
20
+ %w[font_ttf].each do |dep|
21
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../vendor/#{dep}")
22
+ end
23
+
24
+ require 'ttf'
25
+
26
+ module Prawn
27
+ file = __FILE__
28
+ file = File.readlink(file) if File.symlink?(file)
29
+ dir = File.dirname(file)
30
+
31
+ # The base source directory for Prawn as installed on the system
32
+ BASEDIR = File.expand_path(File.join(dir, '..'))
33
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ if RUBY_VERSION < "1.9"
4
+ require "strscan"
5
+
6
+ class String
7
+ alias_method :lines, :to_a
8
+
9
+ def each_char
10
+ scanner, char = StringScanner.new(self), /./mu
11
+ loop { yield(scanner.scan(char) || break) }
12
+ end
13
+ end
14
+
15
+ def ruby_18
16
+ yield
17
+ end
18
+
19
+ def ruby_19
20
+ false
21
+ end
22
+
23
+ else
24
+
25
+ def ruby_18
26
+ false
27
+ end
28
+
29
+ def ruby_19
30
+ yield
31
+ end
32
+
33
+ end
@@ -0,0 +1,334 @@
1
+ # encoding: utf-8
2
+
3
+ # document.rb : Implements PDF document generation for Prawn
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "stringio"
10
+ require "prawn/document/page_geometry"
11
+ require "prawn/document/bounding_box"
12
+ require "prawn/document/text"
13
+ require "prawn/document/table"
14
+
15
+ module Prawn
16
+ class Document
17
+
18
+ include Prawn::Graphics
19
+ include Prawn::Images
20
+ include Text
21
+ include PageGeometry
22
+
23
+ attr_accessor :y, :margin_box
24
+ attr_reader :margins, :page_size, :page_layout
25
+
26
+
27
+ # Creates and renders a PDF document.
28
+ #
29
+ # The block argument is necessary only when you need to make
30
+ # use of a closure.
31
+ #
32
+ # # Using implicit block form and rendering to a file
33
+ # Prawn::Document.generate "foo.pdf" do
34
+ # font "Times-Roman"
35
+ # text "Hello World", :at => [200,720], :size => 32
36
+ # end
37
+ #
38
+ # # Using explicit block form and rendering to a file
39
+ # content = "Hello World"
40
+ # Prawn::Document.generate "foo.pdf" do |pdf|
41
+ # pdf.font "Times-Roman"
42
+ # pdf.text content, :at => [200,720], :size => 32
43
+ # end
44
+ #
45
+ def self.generate(filename,options={},&block)
46
+ pdf = Prawn::Document.new(options)
47
+ block.arity < 1 ? pdf.instance_eval(&block) : yield(pdf)
48
+ pdf.render_file(filename)
49
+ end
50
+
51
+ # Creates a new PDF Document. The following options are available:
52
+ #
53
+ # <tt>:page_size</tt>:: One of the Document::PageGeometry::SIZES [LETTER]
54
+ # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
55
+ # <tt>:on_page_start</tt>:: Optional proc run at each page start
56
+ # <tt>:on_page_stop</tt>:: Optional proc run at each page stop
57
+ # <tt>:left_margin</tt>:: Sets the left margin in points [ 0.5 inch]
58
+ # <tt>:right_margin</tt>:: Sets the right margin in points [ 0.5 inch]
59
+ # <tt>:top_margin</tt>:: Sets the top margin in points [ 0.5 inch]
60
+ # <tt>:bottom_margin</tt>:: Sets the bottom margin in points [0.5 inch]
61
+ # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false]
62
+ #
63
+ #
64
+ # # New document, US Letter paper, portrait orientation
65
+ # pdf = Prawn::Document.new
66
+ #
67
+ # # New document, A4 paper, landscaped
68
+ # pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape)
69
+ #
70
+ # # New document, draws a line at the start of each new page
71
+ # pdf = Prawn::Document.new(:on_page_start =>
72
+ # lambda { |doc| doc.line [0,100], [300,100] } )
73
+ #
74
+ def initialize(options={})
75
+ @objects = []
76
+ @info = ref(:Creator => "Prawn", :Producer => "Prawn")
77
+ @pages = ref(:Type => :Pages, :Count => 0, :Kids => [])
78
+ @root = ref(:Type => :Catalog, :Pages => @pages)
79
+ @page_start_proc = options[:on_page_start]
80
+ @page_stop_proc = options[:on_page_end]
81
+ @page_size = options[:page_size] || "LETTER"
82
+ @page_layout = options[:page_layout] || :portrait
83
+
84
+ @margins = { :left => options[:left_margin] || 36,
85
+ :right => options[:right_margin] || 36,
86
+ :top => options[:top_margin] || 36,
87
+ :bottom => options[:bottom_margin] || 36 }
88
+
89
+ generate_margin_box
90
+
91
+ @bounding_box = @margin_box
92
+
93
+ start_new_page unless options[:skip_page_creation]
94
+ end
95
+
96
+ # Creates and advances to a new page in the document.
97
+ # Runs the <tt>on_page_start</tt> lambda if one was provided at
98
+ # document creation time (See Document.new).
99
+ #
100
+ # Page size, margins, and layout can also be set when generating a
101
+ # new page. These values will become the new defaults for page creation
102
+ #
103
+ # pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
104
+ # pdf.start_new_page(:left_margin => 50, :right_margin => 50)
105
+ #
106
+ def start_new_page(options = {})
107
+ @page_size = options[:size] if options[:size]
108
+ @page_layout = options[:layout] if options[:layout]
109
+
110
+ [:left,:right,:top,:bottom].each do |side|
111
+ if options[:"#{side}_margin"]
112
+ @margins[side] = options[:"#{side}_margin"]
113
+ end
114
+ end
115
+
116
+ finish_page_content if @page_content
117
+ generate_margin_box
118
+ @page_content = ref(:Length => 0)
119
+
120
+ @current_page = ref(:Type => :Page,
121
+ :Parent => @pages,
122
+ :MediaBox => page_dimensions,
123
+ :Contents => @page_content)
124
+ set_current_font
125
+ update_colors
126
+ @pages.data[:Kids] << @current_page
127
+ @pages.data[:Count] += 1
128
+
129
+ add_content "q"
130
+
131
+ @y = @margin_box.absolute_top
132
+ @page_start_proc[self] if @page_start_proc
133
+ end
134
+
135
+ # Returns the number of pages in the document
136
+ #
137
+ # pdf = Prawn::Document.new
138
+ # pdf.page_count #=> 1
139
+ # 3.times { pdf.start_new_page }
140
+ # pdf.page_count #=> 4
141
+ #
142
+ def page_count
143
+ @pages.data[:Count]
144
+ end
145
+
146
+ # Renders the PDF document to string
147
+ #
148
+ def render
149
+ output = StringIO.new
150
+ finish_page_content
151
+
152
+ render_header(output)
153
+ render_body(output)
154
+ render_xref(output)
155
+ render_trailer(output)
156
+ str = output.string
157
+ str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
158
+ str
159
+ end
160
+
161
+ # Renders the PDF document to file.
162
+ #
163
+ # pdf.render_file "foo.pdf"
164
+ #
165
+ def render_file(filename)
166
+ Kernel.const_defined?("Encoding") ? mode = "wb:ASCII-8BIT" : mode = "wb"
167
+ File.open(filename,mode) { |f| f << render }
168
+ end
169
+
170
+ # Returns the current BoundingBox object, which is by default
171
+ # the box represented by the margin box. When called from within
172
+ # a <tt>bounding_box</tt> block, the box defined by that call will
173
+ # be used.
174
+ #
175
+ def bounds
176
+ @bounding_box
177
+ end
178
+
179
+ # Moves up the document by n points
180
+ #
181
+ def move_up(n)
182
+ self.y += n
183
+ end
184
+
185
+ # Moves down the document by n point
186
+ #
187
+ def move_down(n)
188
+ self.y -= n
189
+ end
190
+
191
+ # Moves down the document and then executes a block.
192
+ #
193
+ # pdf.text "some text"
194
+ # pdf.pad_top(100) do
195
+ # pdf.text "This is 100 points below the previous line of text"
196
+ # end
197
+ # pdf.text "This text appears right below the previous line of text"
198
+ #
199
+ def pad_top(y)
200
+ move_down(y)
201
+ yield
202
+ end
203
+
204
+ # Executes a block then moves down the document
205
+ #
206
+ # pdf.text "some text"
207
+ # pdf.pad_bottom(100) do
208
+ # pdf.text "This text appears right below the previous line of text"
209
+ # end
210
+ # pdf.text "This is 100 points below the previous line of text"
211
+ #
212
+ def pad_bottom(y)
213
+ yield
214
+ move_down(y)
215
+ end
216
+
217
+ # Moves down the document by y, executes a block, then moves down the
218
+ # document by y again.
219
+ #
220
+ # pdf.text "some text"
221
+ # pdf.pad(100) do
222
+ # pdf.text "This is 100 points below the previous line of text"
223
+ # end
224
+ # pdf.text "This is 100 points below the previous line of text"
225
+ #
226
+ def pad(y)
227
+ move_down(y)
228
+ yield
229
+ move_down(y)
230
+ end
231
+
232
+
233
+ def mask(*fields) # :nodoc:
234
+ # Stores the current state of the named attributes, executes the block, and
235
+ # then restores the original values after the block has executed.
236
+ # -- I will remove the nodoc if/when this feature is a little less hacky
237
+ stored = {}
238
+ fields.each { |f| stored[f] = send(f) }
239
+ yield
240
+ fields.each { |f| send("#{f}=", stored[f]) }
241
+ end
242
+
243
+ private
244
+
245
+ def generate_margin_box
246
+ old_margin_box = @margin_box
247
+ @margin_box = BoundingBox.new(
248
+ self,
249
+ [ @margins[:left], page_dimensions[-1] - @margins[:top] ] ,
250
+ :width => page_dimensions[-2] - (@margins[:left] + @margins[:right]),
251
+ :height => page_dimensions[-1] - (@margins[:top] + @margins[:bottom])
252
+ )
253
+
254
+ # update bounding box if not flowing from the previous page
255
+ # TODO: This may have a bug where the old margin is restored
256
+ # when the bounding box exits.
257
+ @bounding_box = @margin_box if old_margin_box == @bounding_box
258
+ end
259
+
260
+ def ref(data)
261
+ @objects.push(Prawn::Reference.new(@objects.size + 1, data)).last
262
+ end
263
+
264
+ def add_content(str)
265
+ @page_content << str << "\n"
266
+ end
267
+
268
+ # Add a new type to the current pages ProcSet
269
+ def proc_set(*types)
270
+ @current_page.data[:ProcSet] ||= ref([])
271
+ @current_page.data[:ProcSet].data |= types
272
+ end
273
+
274
+ def page_resources
275
+ @current_page.data[:Resources] ||= {}
276
+ end
277
+
278
+ def page_fonts
279
+ page_resources[:Font] ||= {}
280
+ end
281
+
282
+ def page_xobjects
283
+ page_resources[:XObject] ||= {}
284
+ end
285
+
286
+ def finish_page_content
287
+ @page_stop_proc[self] if @page_stop_proc
288
+ add_content "Q"
289
+ @page_content.data[:Length] = @page_content.stream.size
290
+ end
291
+
292
+ # Write out the PDF Header, as per spec 3.4.1
293
+ def render_header(output)
294
+ # pdf version
295
+ output << "%PDF-1.3\n"
296
+
297
+ # 4 binary chars, as recommended by the spec
298
+ output << "\xFF\xFF\xFF\xFF\n"
299
+ end
300
+
301
+ # Write out the PDF Body, as per spec 3.4.2
302
+ def render_body(output)
303
+ @objects.each do |ref|
304
+ ref.offset = output.size
305
+ output << ref.object
306
+ end
307
+ end
308
+
309
+ # Write out the PDF Cross Reference Table, as per spec 3.4.3
310
+ def render_xref(output)
311
+ @xref_offset = output.size
312
+ output << "xref\n"
313
+ output << "0 #{@objects.size + 1}\n"
314
+ output << "0000000000 65535 f \n"
315
+ @objects.each do |ref|
316
+ output.printf("%010d", ref.offset)
317
+ output << " 00000 n \n"
318
+ end
319
+ end
320
+
321
+ # Write out the PDF Body, as per spec 3.4.4
322
+ def render_trailer(output)
323
+ trailer_hash = {:Size => @objects.size + 1,
324
+ :Root => @root,
325
+ :Info => @info}
326
+
327
+ output << "trailer\n"
328
+ output << Prawn::PdfObject(trailer_hash) << "\n"
329
+ output << "startxref\n"
330
+ output << @xref_offset << "\n"
331
+ output << "%%EOF"
332
+ end
333
+ end
334
+ end