Class: Prawn::Document

Inherits:
Object
  • Object
show all
Includes:
Internals, PageGeometry, Text, Graphics, Images
Defined in:
lib/prawn/font.rb,
lib/prawn/document.rb,
lib/prawn/document/span.rb,
lib/prawn/document/text.rb,
lib/prawn/graphics/cell.rb,
lib/prawn/document/table.rb,
lib/prawn/document/internals.rb,
lib/prawn/document/bounding_box.rb,
lib/prawn/document/page_geometry.rb

Defined Under Namespace

Modules: Internals, PageGeometry, Text Classes: BoundingBox, LazyBoundingBox, Table

Constant Summary

Constants included from PageGeometry

PageGeometry::SIZES

Constants included from Graphics

Graphics::KAPPA

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PageGeometry

#page_dimensions

Methods included from Text

#text, #text_options

Methods included from Images

#image

Methods included from Graphics

#circle_at, #curve, #curve_to, #ellipse_at, #fill, #fill_and_stroke, #horizontal_line, #horizontal_rule, #line, #line_to, #line_width, #line_width=, #move_to, #polygon, #rectangle, #stroke, #vertical_line_at

Methods included from Graphics::Color

#fill_color, hex2rgb, #method_missing, rgb2hex, #stroke_color

Methods included from Internals

#add_content, #page_fonts, #page_resources, #page_xobjects, #proc_set, #ref

Constructor Details

#initialize(options = {}, &block) ⇒ Document

Creates a new PDF Document. The following options are available:

:page_size

One of the Document::PageGeometry::SIZES [LETTER]

:page_layout

Either :portrait or :landscape

:on_page_start

Optional proc run at each page start

:on_page_stop

Optional proc run at each page stop

:left_margin

Sets the left margin in points [ 0.5 inch]

:right_margin

Sets the right margin in points [ 0.5 inch]

:top_margin

Sets the top margin in points [ 0.5 inch]

:bottom_margin

Sets the bottom margin in points [0.5 inch]

:skip_page_creation

Creates a document without starting the first page [false]

:compress

Compresses content streams before rendering them [false]

Usage:

# New document, US Letter paper, portrait orientation
pdf = Prawn::Document.new                            

# New document, A4 paper, landscaped
pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/prawn/document.rb', line 79

def initialize(options={},&block)   
  Prawn.verify_options [:page_size, :page_layout, :on_page_start,
    :on_page_stop, :left_margin, :right_margin, :top_margin,
    :bottom_margin, :skip_page_creation, :compress ], options
    
  @objects = []
  @info    = ref(:Creator => "Prawn", :Producer => "Prawn")
  @pages   = ref(:Type => :Pages, :Count => 0, :Kids => [])  
  @root    = ref(:Type => :Catalog, :Pages => @pages)        
  @page_size       = options[:page_size]   || "LETTER"    
  @page_layout     = options[:page_layout] || :portrait
  @compress        = options[:compress] || false                
  @skip_encoding   = options[:skip_encoding]
  
  text_options.update(options[:text_options] || {}) 
        
  @margins = { :left   => options[:left_margin]   || 36,
               :right  => options[:right_margin]  || 36,  
               :top    => options[:top_margin]    || 36,       
               :bottom => options[:bottom_margin] || 36  }
   
  generate_margin_box
  
  @bounding_box = @margin_box
  
  start_new_page unless options[:skip_page_creation]    
  
  if block
    block.arity < 1 ? instance_eval(&block) : block[self]    
  end 
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Prawn::Graphics::Color

Instance Attribute Details

#margin_boxObject

Returns the value of attribute margin_box.



26
27
28
# File 'lib/prawn/document.rb', line 26

def margin_box
  @margin_box
end

#marginsObject (readonly)

Returns the value of attribute margins.



27
28
29
# File 'lib/prawn/document.rb', line 27

def margins
  @margins
end

#page_layoutObject (readonly)

Returns the value of attribute page_layout.



27
28
29
# File 'lib/prawn/document.rb', line 27

def page_layout
  @page_layout
end

#page_sizeObject (readonly)

Returns the value of attribute page_size.



27
28
29
# File 'lib/prawn/document.rb', line 27

def page_size
  @page_size
end

#yObject

Returns the value of attribute y.



26
27
28
# File 'lib/prawn/document.rb', line 26

def y
  @y
end

Class Method Details

.generate(filename, options = {}, &block) ⇒ Object

Creates and renders a PDF document.

When using the implicit block form, Prawn will evaluate the block within an instance of Prawn::Document, simplifying your syntax. However, please note that you will not be able to reference variables from the enclosing scope within this block.

# Using implicit block form and rendering to a file
Prawn::Document.generate "foo.pdf" do
  font "Times-Roman"   
  text "Hello World", :at => [200,720], :size => 32       
end

If you need to access your local and instance variables, use the explicit block form shown below. In this case, Prawn yields an instance of PDF::Document and the block is an ordinary closure:

# Using explicit block form and rendering to a file   
content = "Hello World"
Prawn::Document.generate "foo.pdf" do |pdf|
  pdf.font "Times-Roman"
  pdf.text content, :at => [200,720], :size => 32
end


53
54
55
56
# File 'lib/prawn/document.rb', line 53

def self.generate(filename,options={},&block)
  pdf = Prawn::Document.new(options,&block)          
  pdf.render_file(filename)
end

Instance Method Details

#bounding_box(*args, &block) ⇒ Object

A bounding box serves two important purposes:

  • Provide bounds for flowing text, starting at a given point

  • Translate the origin (0,0) for graphics primitives, for the purposes

of simplifying coordinate math.

When flowing text, the usage of a bounding box is simple. Text will begin at the point specified, flowing the width of the bounding box. After the block exits, the text drawing position will be moved to the bottom of the bounding box (y - height). If flowing text exceeds the height of the bounding box, the text will be continued on the next page, starting again at the top-left corner of the bounding box.

pdf.bounding_box([100,500], :width => 100, :height => 300) do
  pdf.text "This text will flow in a very narrow box starting" +
   "from [100,500]. The pointer will then be moved to [100,200]" +
   "and return to the margin_box"
end

When translating coordinates, the idea is to allow the user to draw relative to the origin, and then translate their drawing to a specified area of the document, rather than adjust all their drawing coordinates to match this new region.

Take for example two triangles which share one point, drawn from the origin:

pdf.polygon [0,250], [0,0], [150,100]
pdf.polygon [100,0], [150,100], [200,0]

It would be easy enough to translate these triangles to another point, e.g [200,200]

pdf.polygon [200,450], [200,200], [350,300]
pdf.polygon [300,200], [350,300], [400,200]

However, each time you want to move the drawing, you’d need to alter every point in the drawing calls, which as you might imagine, can become tedious.

If instead, we think of the drawing as being bounded by a box, we can see that the image is 200 points wide by 250 points tall.

To translate it to a new origin, we simply select a point at (x,y+height)

Using the [200,200] example:

pdf.bounding_box([200,450], :width => 200, :height => 250) do
  pdf.polygon [0,250], [0,0], [150,100]
  pdf.polygon [100,0], [150,100], [200,0]
end

Notice that the drawing is still relative to the origin. If we want to move this drawing around the document, we simply need to recalculate the top-left corner of the rectangular bounding-box, and all of our graphics calls remain unmodified.

By default, bounding boxes are specified relative to the document’s margin_box (which is itself a bounding box). You can also nest bounding boxes, allowing you to build components which are relative to each other

pdf.bouding_box(, :width => 200, :height => 250) do

pdf.bounding_box([50,200], :width => 50, :height => 50) do
  # a 50x50 bounding box that starts 50 pixels left and 50 pixels down 
  # the parent bounding box.
end

end

If you wish to position the bounding boxes at absolute coordinates rather than relative to the margins or other bounding boxes, you can use canvas()

pdf.canvas do
  pdf.bounding_box([200,450], :width => 200, :height => 250) do
    # positioned at 'real' (200,450)
  end
end

Of course, if you use canvas, you will be responsible for ensuring that you remain within the printable area of your document.



85
86
87
88
89
90
# File 'lib/prawn/document/bounding_box.rb', line 85

def bounding_box(*args, &block)    
  init_bounding_box(block) do |_|
    translate!(args[0])     
    @bounding_box = BoundingBox.new(self, *args)   
  end
end

#boundsObject

Returns the current BoundingBox object, which is by default the box represented by the margin box. When called from within a bounding_box block, the box defined by that call will be used.



180
181
182
# File 'lib/prawn/document.rb', line 180

def bounds
  @bounding_box
end

#bounds=(bounding_box) ⇒ Object

Sets Document#bounds to the BoundingBox provided. If you don’t know why you’d need to do this, chances are, you can ignore this feature



187
188
189
# File 'lib/prawn/document.rb', line 187

def bounds=(bounding_box)
  @bounding_box = bounding_box
end

#canvas(&block) ⇒ Object

A shortcut to produce a bounding box which is mapped to the document’s absolute coordinates, regardless of how things are nested or margin sizes.

pdf.canvas do
  pdf.line pdf.bounds.bottom_left, pdf.bounds.top_right
end


130
131
132
133
134
135
136
137
# File 'lib/prawn/document/bounding_box.rb', line 130

def canvas(&block)     
  init_bounding_box(block, :hold_position => true) do |_|
    @bounding_box = BoundingBox.new(self, [0,page_dimensions[3]], 
      :width => page_dimensions[2], 
      :height => page_dimensions[3] 
    ) 
  end
end

#cell(point, options = {}) ⇒ Object

Builds and renders a Graphics::Cell. A cell is essentially a special-purpose bounding box designed for flowing text within a bordered area. For available options, see Graphics::Cell#new.

Prawn::Document.generate("cell.pdf") do
   cell [100,500], 
     :width => 200,
     :text  => "The rain in Spain falls mainly on the plains"
end


22
23
24
25
# File 'lib/prawn/graphics/cell.rb', line 22

def cell(point, options={})
  Prawn::Graphics::Cell.new(
    options.merge(:document => self, :point => point)).draw
end

#compression_enabled?Boolean

Returns true if content streams will be compressed before rendering, false otherwise

Returns:

  • (Boolean)


257
258
259
# File 'lib/prawn/document.rb', line 257

def compression_enabled?
  !!@compress
end

#font(name = nil, options = {}) ⇒ Object

Sets the current font.

The single parameter must be a string. It can be one of the 14 built-in fonts supported by PDF, or the location of a TTF file. The BUILT_INS array specifies the valid built in font values.

pdf.font "Times-Roman"
pdf.font "Chalkboard.ttf"

If a ttf font is specified, the full file will be embedded in the rendered PDF. This should be your preferred option in most cases. It will increase the size of the resulting file, but also make it more portable.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/prawn/font.rb', line 24

def font(name=nil, options={}) 
  if name     
    if font_families.key?(name)
      ff = name                                                      
      name = font_families[name][options[:style] || :normal]
    end 
    Prawn::Font.register(name,:for => self, :family => ff) unless font_registry[name]      
    font_registry[name].add_to_current_page
    @font_name = name   
  elsif @font_name.nil?                                              
    Prawn::Font.register("Helvetica", :for => self, :family => "Helvetica") 
    @font_name = "Helvetica"             
  end  
  font_registry[@font_name] 
end

#font_familiesObject

Hash that maps font family names to their styled individual font names

To add support for another font family, append to this hash, e.g:

pdf.font_families.update(
 "MyTrueTypeFamily" => { :bold        => "foo-bold.ttf", 
                         :italic      => "foo-italic.ttf",
                         :bold_italic => "foo-bold-italic.ttf",
                         :normal      => "foo.ttf" })

This will then allow you to use the fonts like so:

pdf.font("MyTrueTypeFamily", :style => :bold)   
pdf.text "Some bold text"
pdf.font("MyTrueTypeFamily")
pdf.text "Some normal text"

This assumes that you have appropriate TTF fonts for each style you wish to support.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/prawn/font.rb', line 66

def font_families 
  @font_families ||= Hash.new { |h,k| h[k] = {} }.merge!(      
    { "Courier"     => { :bold        => "Courier-Bold",
                         :italic      => "Courier-Oblique",
                         :bold_italic => "Courier-BoldOblique",
                         :normal      => "Courier" },       
       
      "Times-Roman" => { :bold         => "Times-Bold",
                         :italic       => "Times-Italic",
                         :bold_italic  => "Times-BoldItalic",
                         :normal       => "Times-Roman" },        
       
      "Helvetica"   => { :bold         => "Helvetica-Bold",
                         :italic       => "Helvetica-Oblique",
                         :bold_italic  => "Helvetica-BoldOblique",
                         :normal       => "Helvetica" }        
    }) 
end

#font_registryObject

Hash of Font objects keyed by names



42
43
44
# File 'lib/prawn/font.rb', line 42

def font_registry #:nodoc:
  @font_registry ||= {}
end

A footer is a LazyBoundingBox drawn relative to the margins that can be repeated on every page of the document.

Unless :width or :height are specified, the margin_box width and height

footer [margin_box.left, margin_box.bottom + 25] do
  stroke_horizontal_rule
  text "And here's a sexy footer", :size => 16
end


165
166
167
# File 'lib/prawn/document/bounding_box.rb', line 165

def footer(top_left,options={},&block)       
  @footer = repeating_page_element(top_left,options,&block)
end

#header(top_left, options = {}, &block) ⇒ Object

A header is a LazyBoundingBox drawn relative to the margins that can be repeated on every page of the document.

Unless :width or :height are specified, the margin_box width and height

 header margin_box.top_left do 
  text "Here's My Fancy Header", :size => 25, :align => :center   
  stroke_horizontal_rule
end


150
151
152
# File 'lib/prawn/document/bounding_box.rb', line 150

def header(top_left,options={},&block)   
  @header = repeating_page_element(top_left,options,&block)
end

#lazy_bounding_box(*args, &block) ⇒ Object

A LazyBoundingBox is simply a BoundingBox with an action tied to it to be executed later. The lazy_bounding_box method takes the same arguments as bounding_box, but returns a LazyBoundingBox object instead of executing the code block directly.

You can then call LazyBoundingBox#draw at any time (or multiple times if you wish), and the contents of the block will then be run. This can be useful for assembling repeating page elements or reusable components.

file = "lazy_bounding_boxes.pdf"
Prawn::Document.generate(file, :skip_page_creation => true) do                    
  point = [bounds.right-50, bounds.bottom + 25]
  page_counter = lazy_bounding_box(point, :width => 50) do   
    text "Page: #{page_count}"
  end 

  10.times do         
   start_new_page
    text "Some text"  
    page_counter.draw
  end
end


116
117
118
119
120
121
# File 'lib/prawn/document/bounding_box.rb', line 116

def lazy_bounding_box(*args,&block)
  translate!(args[0])  
  box = LazyBoundingBox.new(self,*args)
  box.action(&block)
  return box 
end

#mask(*fields) ⇒ Object

:nodoc:



244
245
246
247
248
249
250
251
252
# File 'lib/prawn/document.rb', line 244

def mask(*fields) # :nodoc:
 # Stores the current state of the named attributes, executes the block, and
 # then restores the original values after the block has executed.
 # -- I will remove the nodoc if/when this feature is a little less hacky
  stored = {}
  fields.each { |f| stored[f] = send(f) }
  yield
  fields.each { |f| send("#{f}=", stored[f]) }
end

#move_down(n) ⇒ Object

Moves down the document by n point



199
200
201
# File 'lib/prawn/document.rb', line 199

def move_down(n)
  self.y -= n
end

#move_up(n) ⇒ Object

Moves up the document by n points



193
194
195
# File 'lib/prawn/document.rb', line 193

def move_up(n)
  self.y += n
end

#pad(y) ⇒ Object

Moves down the document by y, executes a block, then moves down the document by y again.

pdf.text "some text"
pdf.pad(100) do
  pdf.text "This is 100 points below the previous line of text"  
end
pdf.text "This is 100 points below the previous line of text"


238
239
240
241
242
# File 'lib/prawn/document.rb', line 238

def pad(y)
  move_down(y)
  yield
  move_down(y)
end

#pad_bottom(y) ⇒ Object

Executes a block then moves down the document

pdf.text "some text"
pdf.pad_bottom(100) do
  pdf.text "This text appears right below the previous line of text"
end
pdf.text "This is 100 points below the previous line of text"


224
225
226
227
# File 'lib/prawn/document.rb', line 224

def pad_bottom(y)
  yield
  move_down(y)
end

#pad_top(y) ⇒ Object

Moves down the document and then executes a block.

pdf.text "some text"
pdf.pad_top(100) do
  pdf.text "This is 100 points below the previous line of text"
end
pdf.text "This text appears right below the previous line of text"


211
212
213
214
# File 'lib/prawn/document.rb', line 211

def pad_top(y)
  move_down(y)
  yield
end

#page_countObject

Returns the number of pages in the document

pdf = Prawn::Document.new
pdf.page_count #=> 1
3.times { pdf.start_new_page }
pdf.page_count #=> 4


147
148
149
# File 'lib/prawn/document.rb', line 147

def page_count
  @pages.data[:Count]
end

#renderObject

Renders the PDF document to string



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/prawn/document.rb', line 153

def render
  output = StringIO.new
  finish_page_content

  render_header(output)
  render_body(output)
  render_xref(output)
  render_trailer(output)
  str = output.string 
  str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
  str
end

#render_file(filename) ⇒ Object

Renders the PDF document to file.

pdf.render_file "foo.pdf"


170
171
172
173
# File 'lib/prawn/document.rb', line 170

def render_file(filename)
  Kernel.const_defined?("Encoding") ? mode = "wb:ASCII-8BIT" : mode = "wb"
  File.open(filename,mode) { |f| f << render }
end

#span(width, options = {}) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/prawn/document/span.rb', line 3

def span(width, options={})
  Prawn.verify_options [:position], options
  original_position = self.y      
  
  # FIXME: How many effing times do I want to write this same code?
  left_boundary = case(options[:position] || :left)
  when :left
    margin_box.absolute_left
  when :center
    margin_box.absolute_left + margin_box.width / 2.0 - width /2.0
  when :right
    margin_box.absolute_right - width
  when Numeric
    margin_box.absolute_left + options[:position]
  else
    raise ArgumentError, "Invalid option for :position"
  end
  
  # we need to bust out of whatever nested bounding boxes we're in.
  canvas do
    bounding_box([left_boundary, 
                  margin_box.absolute_top], :width => width) do
      self.y = original_position
      yield
    end
  end          
end

#start_new_page(options = {}) ⇒ Object

Creates and advances to a new page in the document.

Page size, margins, and layout can also be set when generating a new page. These values will become the new defaults for page creation

pdf.start_new_page(:size => "LEGAL", :layout => :landscape)    
pdf.start_new_page(:left_margin => 50, :right_margin => 50)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/prawn/document.rb', line 119

def start_new_page(options = {})      
   @page_size   = options[:size] if options[:size]
   @page_layout = options[:layout] if options[:layout]
                                         
   [:left,:right,:top,:bottom].each do |side|  
     if options[:"#{side}_margin"] 
       @margins[side] = options[:"#{side}_margin"]   
     end
   end
   
   finish_page_content if @page_content  
   build_new_page_content

   @pages.data[:Kids] << @current_page
   @pages.data[:Count] += 1 
 
   add_content "q"   
   
   @y = @bounding_box.absolute_top        
end

#table(data, options = {}) ⇒ Object

Builds and renders a Document::Table object from raw data. For details on the options that can be passed, see Document::Table.new

data = [["Gregory","Brown"],["James","Healy"],["Jia","Wu"]]

Prawn::Document.generate("table.pdf") do

  # Default table, without headers
  table(data)

  # Default table with headers
  table data, :headers => ["First Name", "Last Name"]

  # Very close to PDF::Writer's default SimpleTable output
  table data, :headers            => ["First Name", "Last Name"],
              :font_size          => 10,
              :vertical_padding   => 2,
              :horizontal_padding => 5,
              :position           => :center,
              :row_colors         => :pdf_writer,

  # Grid border style with explicit column widths.
  table data, :border_style => :grid,
              :widths       => { 0 => 100, 1 => 150 }

end


40
41
42
# File 'lib/prawn/document/table.rb', line 40

def table(data,options={})           
  Prawn::Document::Table.new(data,self,options).draw
end