prawn 0.1.0

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