lib/pdf/wrapper.rb in pdf-wrapper-0.1.3 vs lib/pdf/wrapper.rb in pdf-wrapper-0.2.0
- old
+ new
@@ -1,10 +1,12 @@
# coding: utf-8
require 'stringio'
require 'pdf/core'
require 'pdf/errors'
+require 'tempfile'
+require 'fileutils'
require File.dirname(__FILE__) + "/wrapper/graphics"
require File.dirname(__FILE__) + "/wrapper/images"
require File.dirname(__FILE__) + "/wrapper/loading"
require File.dirname(__FILE__) + "/wrapper/table"
@@ -16,32 +18,53 @@
require 'cairo'
module PDF
# Create PDF files by using the cairo and pango libraries.
#
- # Rendering to a file:
+ # == Rendering to a file
#
# require 'pdf/wrapper'
- # pdf = PDF::Wrapper.new(:paper => :A4)
+ # pdf = PDF::Wrapper.new("somefile.pdf", :paper => :A4)
# pdf.text "Hello World"
- # pdf.render_to_file("wrapper.pdf")
+ # pdf.finish
#
- # Rendering to a string:
+ # == Rendering to a file (alternative)
#
# require 'pdf/wrapper'
- # pdf = PDF::Wrapper.new(:paper => :A4)
+ # File.open("somefile.pdf", "w") do |output|
+ # pdf = PDF::Wrapper.new(output, :paper => :A4)
+ # pdf.text "Hello World"
+ # pdf.finish
+ # end
+ #
+ # == Rendering to a string
+ #
+ # require 'pdf/wrapper'
+ # output = StringIO.new
+ # pdf = PDF::Wrapper.new(output, :paper => :A4)
# pdf.text "Hello World", :font_size => 16
- # puts pdf.render
+ # pdf.finish
+ # puts output.string
#
- # Changing the default font:
+ # == Block format
#
+ # Avoid the need to call finish()
+ #
# require 'pdf/wrapper'
- # pdf = PDF::Wrapper.new(:paper => :A4)
+ # PDF::Wrapper.open("somefile.pdf", :paper => :A4)
+ # pdf.text "Hello World", :font_size => 16
+ # end
+ #
+ # == Changing the default font
+ #
+ # require 'pdf/wrapper'
+ # pdf = PDF::Wrapper.new("file.pdf", :paper => :A4)
# pdf.font("Monospace")
# pdf.text "Hello World", :font => "Sans Serif", :font_size => 18
# pdf.text "Pretend this is a code sample"
- # puts pdf.render
+ # pdf.finish
+ #
class Wrapper
attr_reader :page
# borrowed from PDF::Writer
@@ -72,10 +95,13 @@
:LEGAL => [612.00, 1008.00], :FOLIO => [612.00, 936.00],
:EXECUTIVE => [521.86, 756.00]
}
# create a new PDF::Wrapper class to compose a PDF document
+ # Params:
+ # <tt>output</tt>:: Where to render the PDF to. Can be a string containing a filename,
+ # or an IO object (File, StringIO, etc)
# Options:
# <tt>:paper</tt>:: The paper size to use (default :A4)
# <tt>:orientation</tt>:: :portrait (default) or :landscape
# <tt>:background_color</tt>:: The background colour to use (default :white)
# <tt>:margin_top</tt>:: The size of the default top margin (default 5% of page)
@@ -83,55 +109,64 @@
# <tt>:margin_left</tt>:: The size of the default left margin (default 5% of page)
# <tt>:margin_right</tt>:: The size of the default right margin (default 5% of page)
# <tt>:template</tt>:: The path to an image file. If specified, the first page of the document will use the specified image as a template.
# The page will be sized to match the template size. The use templates on subsequent pages, see the options for
# start_new_page.
- def initialize(opts={})
+ def initialize(*args)
# TODO: Investigate ways of using the cairo transform/translate/scale functionality to
# reduce the amount of irritating co-ordinate maths the user of PDF::Wrapper (ie. me!)
# is required to do.
# - translate the pdf body width so that it's 1.0 wide and 1.0 high?
# TODO: find a way to add metadata (title, author, subject, etc) to the output file
# currently no way to specify this in cairo.
- # tentatively scheduled for cairo 1.8 - see:
+ # tentatively scheduled for cairo 1.10 - see:
# - http://cairographics.org/roadmap/
# - http://lists.cairographics.org/archives/cairo/2007-September/011441.html
# - http://lists.freedesktop.org/archives/cairo/2006-April/006809.html
# ensure we have recentish cairo bindings
raise "Ruby Cairo bindings version #{Cairo::BINDINGS_VERSION.join(".")} is too low. At least 1.5 is required" if Cairo::BINDINGS_VERSION.to_s < "150"
+ if args.size == 0
+ opts = {}
+ output = StringIO.new
+ warn "WARNING: deprecated call to PDF::Wrapper constructor. Check API documentation on new compulsory parameter"
+ elsif args.size == 1
+ if args.first.kind_of?(Hash)
+ opts = *args
+ output = StringIO.new
+ warn "WARNING: deprecated call to PDF::Wrapper constructor. Check API documentation on new compulsory parameter"
+ else
+ output = args.first
+ opts = {}
+ end
+ elsif args.size == 2
+ output, opts = *args
+ else
+ raise ArgumentError, 'Invalid parameters passed to constructor'
+ end
+
options = {:paper => :A4,
:orientation => :portrait,
:background_color => :white
}
options.merge!(opts)
# test for invalid options
- options.assert_valid_keys(:paper, :orientation, :background_color, :margin_left, :margin_right, :margin_top, :margin_bottom, :template)
- options[:paper] = options[:paper].to_sym
- raise ArgumentError, "Invalid paper option" unless PAGE_SIZES.include?(options[:paper])
+ options.assert_valid_keys(:paper, :orientation, :background_color, :margin_left, :margin_right,
+ :margin_top, :margin_bottom, :io, :template)
- # set page dimensions
- if options[:orientation].eql?(:portrait)
- @page_width = PAGE_SIZES[options[:paper]][0]
- @page_height = PAGE_SIZES[options[:paper]][1]
- elsif options[:orientation].eql?(:landscape)
- @page_width = PAGE_SIZES[options[:paper]][1]
- @page_height = PAGE_SIZES[options[:paper]][0]
- else
- raise ArgumentError, "Invalid orientation"
- end
+ set_dimensions(options[:orientation], options[:paper])
# set page margins and dimensions of usable canvas
@margin_left = options[:margin_left] || (@page_width * 0.05).ceil
@margin_right = options[:margin_right] || (@page_width * 0.05).ceil
@margin_top = options[:margin_top] || (@page_height * 0.05).ceil
@margin_bottom = options[:margin_bottom] || (@page_height * 0.05).ceil
# initialize some cairo objects to draw on
- @output = StringIO.new
+ @output = output
@surface = Cairo::PDFSurface.new(@output, @page_width, @page_height)
@context = Cairo::Context.new(@surface)
# set the background colour
color(options[:background_color])
@@ -156,10 +191,25 @@
# move the cursor to the top left of the usable canvas
reset_cursor
end
+ # convenience method, takes the same arguments as the constructor along with a block,
+ # and automatically finishes the PDF for you.
+ #
+ #= Usage
+ #
+ # PDF::Wrapper.open("somefile.pdf") do |pdf|
+ # pdf.text "hi!"
+ # end
+ #
+ def self.open(output, options = {}, &block)
+ pdf = PDF::Wrapper.new(output, options)
+ yield pdf
+ pdf.finish
+ end
+
#####################################################
# Functions relating to calculating various page dimensions
#####################################################
# Returns the x value of the left margin
@@ -229,10 +279,18 @@
# returns 2 values - x,y
def current_point
@context.current_point
end
+ def x
+ @context.current_point.first
+ end
+
+ def y
+ @context.current_point.last
+ end
+
def margin_bottom
device_y_to_user_y(@margin_bottom).to_i
end
def margin_left
@@ -303,31 +361,34 @@
# Functions relating to generating the final document
#####################################################
# render the PDF and return it as a string
def render
- # finalise the document, then convert the StringIO object it was rendered to
- # into a string
+ # TODO: remove this method at some point. Deprecation started on 15th September 2008
+ warn "WARNING: render() is deprecated. See documentation for PDF::Wrapper#initialize for more information"
finish
- return @output.string
+ case @output
+ when StringIO then return @output.string
+ when File then return File.read(@output.path)
+ else
+ return File.read(@output)
+ end
end
- def render_to_file(filename) #nodoc
- # TODO: remove this at some point
- warn "WARNING: render_to_file() is deprecated, please use render_file()"
- render_file filename
- end
-
# save the rendered PDF to a file
def render_file(filename)
+ # TODO: remove this method at some point. Deprecation started on 15th September 2008
+ warn "WARNING: render_file() is deprecated. See documentation for PDF::Wrapper#initialize for more information"
finish
-
- # write each line from the StringIO object it was rendered to into the
- # requested file
- File.open(filename, "w") do |of|
- @output.rewind
- @output.each_line { |line| of.write(line) }
+ case @output
+ when StringIO then
+ File.open(filename, "w") do |of|
+ of.write(@output.string)
+ end
+ when File then return FileUtils.cp(@output.path, filename)
+ else
+ return FileUtils.cp(@output, filename)
end
end
#####################################################
# Misc Functions
@@ -447,17 +508,34 @@
# reset the cursor by moving it to the top left of the useable section of the page
def reset_cursor
@context.move_to(margin_left,margin_top)
end
+ def finish
+ # finalise the document
+ @context.show_page
+ @context.target.finish
+ #@output.close if io_output?
+ @surface.finish
+ #@surface.destroy
+ #@context.destroy
+ self
+ rescue Cairo::SurfaceFinishedError
+ # do nothing, we're happy that the surfaced has been finished
+ end
+
# returns true if the PDF has already been rendered, false if it hasn't.
# Due to limitations of the underlying libraries, content cannot be
# added to a PDF once it has been rendered.
#
def finished?
- @output.seek(@output.size - 6)
- bytes = @output.read(6)
+ if io_output?
+ @output.seek(@output.size - 6)
+ bytes = @output.read(6)
+ else
+ bytes = @output[-6,6]
+ end
bytes == "%%EOF\n" ? true : false
end
# add the same elements to multiple pages. Useful for adding items like headers, footers and
# watermarks.
@@ -486,16 +564,20 @@
end
# move to the next page
#
# options:
+ # <tt>:paper</tt>:: The paper size to use (default: same as the previous page)
+ # <tt>:orientation</tt>:: :portrait or :landscape (default: same as the previous page)
# <tt>:pageno</tt>:: If specified, the current page number will be set to that. By default, the page number will just increment.
# <tt>:template</tt>:: The path to an image file. If specified, the new page will use the specified image as a template. The page will be sized to match the template size
#
def start_new_page(opts = {})
- opts.assert_valid_keys(:pageno, :template)
+ opts.assert_valid_keys(:paper, :orientation, :pageno, :template)
+ set_dimensions(opts[:orientation], opts[:paper])
+
@context.show_page
if opts[:template]
w, h = image_dimensions(opts[:template])
@surface.set_size(w, h)
@@ -520,16 +602,35 @@
end
end
private
- def finish
- # finalise the document
- @context.show_page
- @context.target.finish
- rescue Cairo::SurfaceFinishedError
- # do nothing, we're happy that the surfaced has been finished
+ def set_dimensions(orientation, paper)
+ # use the defaults if none were provided
+ orientation ||= @orientation
+ paper ||= @paper
+
+ # safety check
+ orientation = orientation.to_sym
+ paper = paper.to_sym
+
+ raise ArgumentError, "Unrecognised paper size (#{paper})" if PAGE_SIZES[paper].nil?
+
+ # set page dimensions
+ if orientation.eql?(:portrait)
+ @page_width = PAGE_SIZES[paper][0]
+ @page_height = PAGE_SIZES[paper][1]
+ elsif orientation.eql?(:landscape)
+ @page_width = PAGE_SIZES[paper][1]
+ @page_height = PAGE_SIZES[paper][0]
+ else
+ raise ArgumentError, "Invalid orientation"
+ end
+
+ # make the new values the defaults
+ @orientation = orientation
+ @paper = paper
end
# runs the code in block, passing it a hash of options that might be
# required
def call_repeating_element(spec, block)
@@ -553,9 +654,17 @@
{ :left => x,
:top => y,
:width => points_to_right_margin(x),
:height => points_to_bottom_margin(y)
}
+ end
+
+ def io_output?
+ if @output.respond_to?(:write) && @output.respond_to?(:read)
+ true
+ else
+ false
+ end
end
# save and restore the cursor position around a block
def save_coords(&block)
origx, origy = current_point