Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

boardiq / prawn   ruby

Repository URL to install this package:

/ lib / prawn / document.rb

# encoding: utf-8

# document.rb : Implements PDF document generation for Prawn
#
# Copyright April 2008, Gregory Brown.  All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.

require "stringio"
require "prawn/document/page_geometry"
require "prawn/document/bounding_box"
require "prawn/document/column_box"
require "prawn/document/internals"
require "prawn/document/span"
require "prawn/document/snapshot"
require "prawn/document/graphics_state"

module Prawn

  # The Prawn::Document class is how you start creating a PDF document.
  #
  # There are three basic ways you can instantiate PDF Documents in Prawn, they
  # are through assignment, implicit block or explicit block.  Below is an exmple
  # of each type, each example does exactly the same thing, makes a PDF document
  # with all the defaults and puts in the default font "Hello There" and then
  # saves it to the current directory as "example.pdf"
  #
  # For example, assignment can be like this:
  #
  #   pdf = Prawn::Document.new
  #   pdf.text "Hello There"
  #   pdf.render_file "example.pdf"
  #
  # Or you can do an implied block form:
  #
  #   Prawn::Document.generate "example.pdf" do
  #     text "Hello There"
  #   end
  #
  # Or if you need to access a variable outside the scope of the block, the
  # explicit block form:
  #
  #   words = "Hello There"
  #   Prawn::Document.generate "example.pdf" do |pdf|
  #     pdf.text words
  #   end
  #
  # Usually, the block forms are used when you are simply creating a PDF document
  # that you want to immediately save or render out.
  #
  # See the new and generate methods for further details on the above.
  #
  class Document
    include Prawn::Document::Internals
    include Prawn::Core::Annotations
    include Prawn::Core::Destinations
    include Prawn::Document::Snapshot
    include Prawn::Document::GraphicsState
    include Prawn::Document::Security
    include Prawn::Text
    include Prawn::Graphics
    include Prawn::Images
    include Prawn::Stamp
    include Prawn::SoftMask

    # Any module added to this array will be included into instances of
    # Prawn::Document at the per-object level.  These will also be inherited by
    # any subclasses.
    #
    # Example:
    #
    #   module MyFancyModule
    #
    #     def party!
    #       text "It's a big party!"
    #     end
    #
    #   end
    #
    #   Prawn::Document.extensions << MyFancyModule
    #
    #   Prawn::Document.generate("foo.pdf") do
    #     party!
    #   end
    #
    def self.extensions
      @extensions ||= []
    end

    def self.inherited(base) #:nodoc:
      extensions.each { |e| base.extensions << e }
    end

    # 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 "example.pdf" do
    #     # self here is set to the newly instantiated Prawn::Document
    #     # and so any variables in the outside scope are unavailable
    #     font "Times-Roman"
    #     draw_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 "example.pdf" do |pdf|
    #     # self here is left alone
    #     pdf.font "Times-Roman"
    #     pdf.draw_text content, :at => [200,720], :size => 32
    #   end
    #
    def self.generate(filename,options={},&block)
      pdf = new(options,&block)
      pdf.render_file(filename)
    end

    # Creates a new PDF Document.  The following options are available (with
    # the default values marked in [])
    #
    # <tt>:page_size</tt>:: One of the Document::PageGeometry sizes [LETTER]
    # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
    # <tt>:margin</tt>:: Sets the margin on all sides in points [0.5 inch]
    # <tt>:left_margin</tt>:: Sets the left margin in points [0.5 inch]
    # <tt>:right_margin</tt>:: Sets the right margin in points [0.5 inch]
    # <tt>:top_margin</tt>:: Sets the top margin in points [0.5 inch]
    # <tt>:bottom_margin</tt>:: Sets the bottom margin in points [0.5 inch]
    # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false]
    # <tt>:compress</tt>:: Compresses content streams before rendering them [false]
    # <tt>:optimize_objects</tt>:: Reduce number of PDF objects in output, at expense of render time [false]
    # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
    # <tt>:background_scale</tt>:: Backgound image scale [1] [nil]
    # <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil]
    # <tt>:template</tt>:: The path to an existing PDF file to use as a template [nil]
    #
    # Setting e.g. the :margin to 100 points and the :left_margin to 50 will result in margins
    # of 100 points on every side except for the left, where it will be 50.
    #
    # The :margin can also be an array much like CSS shorthand:
    #
    #   # Top and bottom are 20, left and right are 100.
    #   :margin => [20, 100]
    #   # Top is 50, left and right are 100, bottom is 20.
    #   :margin => [50, 100, 20]
    #   # Top is 10, right is 20, bottom is 30, left is 40.
    #   :margin => [10, 20, 30, 40]
    #
    # Additionally, :page_size can be specified as a simple two value array giving
    # the width and height of the document you need in PDF Points.
    #
    # 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)
    #
    #   # New document, Custom size
    #   pdf = Prawn::Document.new(:page_size => [200, 300])
    #
    #   # New document, with background
    #   pdf = Prawn::Document.new(:background => "#{Prawn::DATADIR}/images/pigs.jpg")
    #
    def initialize(options={},&block)
      options = options.dup

      Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
        :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
        :compress, :skip_encoding, :background, :info,
        :optimize_objects, :template], options

      # need to fix, as the refactoring breaks this
      # raise NotImplementedError if options[:skip_page_creation]

      self.class.extensions.reverse_each { |e| extend e }
      @internal_state = Prawn::Core::DocumentState.new(options)
      @internal_state.populate_pages_from_store(self)
      min_version(state.store.min_version) if state.store.min_version

      @background = options[:background]
      @background_scale = options[:background_scale] || 1
      @font_size  = 12

      @bounding_box  = nil
      @margin_box    = nil

      @page_number = 0

      options[:size] = options.delete(:page_size)
      options[:layout] = options.delete(:page_layout)

      if options[:template]
        fresh_content_streams(options)
        go_to_page(1)
      else
        if options[:skip_page_creation] || options[:template]
          start_new_page(options.merge(:orphan => true))
        else
          start_new_page(options)
        end
      end

      @bounding_box = @margin_box

      if block
        block.arity < 1 ? instance_eval(&block) : block[self]
      end
    end

    attr_accessor :margin_box
    attr_reader   :margins, :y
    attr_writer   :font_size
    attr_accessor :page_number

    def state
      @internal_state
    end

    def page
      state.page
    end

    # 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 #=> Starts new page keeping current values
    #   pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
    #   pdf.start_new_page(:left_margin => 50, :right_margin => 50)
    #   pdf.start_new_page(:margin => 100)
    #
    # A template for a page can be specified by pointing to the path of and existing pdf.
    # One can also specify which page of the template which defaults otherwise to 1.
    #
    #  pdf.start_new_page(:template => multipage_template.pdf, :template_page => 2)
    #
    # Note: templates get indexed by either the object_id of the filename or stream
    # entered so that if you reuse the same template multiple times be sure to use the
    # same instance for more efficient use of resources and smaller rendered pdfs.
    def start_new_page(options = {})
      @template = options[:template] if options[:template]
      options[:template] = @template.clone if @template && !options[:template_page]

      if last_page = state.page
        last_page_size     = last_page.size
        last_page_layout   = last_page.layout
        last_page_margins  = last_page.margins
      end

      page_options = {:size => options[:size] || last_page_size,
                      :layout  => options[:layout] || last_page_layout,
                      :margins => last_page_margins }

      if last_page
        new_graphic_state = last_page.graphic_state.dup
        #erase the color space so that it gets reset on new page for fussy pdf-readers
        new_graphic_state.color_space = {}
        page_options.merge!(:graphic_state => new_graphic_state)
      end
      merge_template_options(page_options, options) if options[:template]

      state.page = Prawn::Core::Page.new(self, page_options)

      apply_margin_options(options)
      generate_margin_box

      # Reset the bounding box if the new page has different size or layout
      if last_page && (last_page.size != state.page.size ||
                       last_page.layout != state.page.layout)
        @bounding_box = @margin_box
      end

      state.page.new_content_stream if options[:template]
      use_graphic_settings(options[:template])

      unless options[:orphan]
        state.page.dictionary.data[:Parent] = last_page.dictionary.data[:Parent] if last_page
        state.insert_page(state.page, @page_number)
        @page_number += 1

        canvas { image(@background, :scale => @background_scale, :at => bounds.top_left) } if @background
        @y = @bounding_box.absolute_top

        float do
          state.on_page_create_action(self)
        end
      end
    end

    # 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
    #
    def page_count
      state.page_count
    end

    # Re-opens the page with the given (1-based) page number so that you can
    # draw on it.
    #
    # See Prawn::Document#number_pages for a sample usage of this capability.
    #
    def go_to_page(k)
      @page_number = k
      state.page = state.pages[k-1]
      generate_margin_box
      @y = @bounding_box.absolute_top
    end

    def y=(new_y)
      @y = new_y
      bounds.update_height
    end

    # The current y drawing position relative to the innermost bounding box,
    # or to the page margins at the top level.
    #
    def cursor
      y - bounds.absolute_bottom
    end

    # Moves to the specified y position in relative terms to the bottom margin.
    #
    def move_cursor_to(new_y)
      self.y = new_y + bounds.absolute_bottom
    end

    # Executes a block and then restores the original y position. If new pages
    # were created during this block, it will teleport back to the original
    # page when done.
    #
    #   pdf.text "A"
Loading ...