lib/pdf/wrapper.rb in pdf-wrapper-0.1.0 vs lib/pdf/wrapper.rb in pdf-wrapper-0.1.3

- old
+ new

@@ -1,31 +1,21 @@ # coding: utf-8 require 'stringio' require 'pdf/core' +require 'pdf/errors' require File.dirname(__FILE__) + "/wrapper/graphics" require File.dirname(__FILE__) + "/wrapper/images" require File.dirname(__FILE__) + "/wrapper/loading" require File.dirname(__FILE__) + "/wrapper/table" require File.dirname(__FILE__) + "/wrapper/text" +require File.dirname(__FILE__) + "/wrapper/page" -# try to load cairo from the standard places, but don't worry if it fails, -# we'll try to find it via rubygems -begin - require 'cairo' -rescue LoadError - begin - require 'rubygems' - gem 'cairo', '>=1.5' - require 'cairo' - rescue Gem::LoadError - raise LoadError, "Could not find the ruby cairo bindings in the standard locations or via rubygems. Check to ensure they're installed correctly" - rescue LoadError - raise LoadError, "Could not load rubygems" - end -end +require 'rubygems' +gem 'cairo', '>=1.5' +require 'cairo' module PDF # Create PDF files by using the cairo and pango libraries. # # Rendering to a file: @@ -287,41 +277,15 @@ # captioned_image("hobbit.svg", "Hobbit", 100, 400) # captioned_image("elf.svg", "Elf", 100, 400) def translate(x, y, &block) @context.save do @context.translate(x, y) + move_to(0,0) yield end end - # all code wrapped in the block passed to this function will have co-ordinates - # and distances (width/height) multiplied by these values before being used - # - # Divide everything by 2 - # - # pdf.scale(0.5, 0.5) do - # ... - # end - # - # Make the page 1.0 wide and 1.0 tall, so co-ordinates and distances - # can be specified as percentages (0.5 == 50%, etc) - # - # pdf.scale(pdf.page_width.to_f, pdf.page_height.to_f) do - # ... - # end - # - def scale(w, h, &block) - @context.save do - @context.scale(w, h) - - # set the line width again so that it's set relative to the current - # scale factor - line_width @line_width - yield - end - end - # change the default colour used to draw on the canvas # # Parameters: # <tt>c</tt>:: either a colour symbol recognised by rcairo (:red, :blue, :black, etc) or # an array with 3-4 integer elements. The first 3 numbers are red, green and @@ -341,20 +305,23 @@ # 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 - @context.show_page - @context.target.finish + finish return @output.string 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_to_file(filename) - # finalise the document - @context.show_page - @context.target.finish + def render_file(filename) + finish # write each line from the StringIO object it was rendered to into the # requested file File.open(filename, "w") do |of| @output.rewind @@ -364,41 +331,155 @@ ##################################################### # Misc Functions ##################################################### - def pad(n) + # move down the canvas by n points + # returns the new y position + # + def move_down(n) x, y = current_point - move_to(x, y + n) - y + n + newy = y + n + move_to(x, newy) + newy end + # move up the canvas by n points + # returns the new y position + # + def move_up(n) + x, y = current_point + newy = y - n + move_to(x, newy) + newy + end + + # move left across the canvas by n points + # returns the new x position + # + def move_left(n) + x, y = current_point + newx = x - n + move_to(newx, y) + newx + end + + # move right across the canvas by n points + # returns the new x position + # + def move_right(n) + x, y = current_point + newx = x + n + move_to(newx, y) + newx + end + + # 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" + # + def pad_top(n) + move_down n + yield + end + + # 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" + # + def pad_bottom(n) + yield + move_down n + end + + # 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" + # + def pad(n) + if block_given? + move_down n + yield + move_down n + else + move_down n + end + end + + # Moves right across the document by n, executes a block, then moves back + # left by the same amount + # + # pdf.text "some text" + # pdf.indent(50) do + # pdf.text "This starts 50 points right the previous line of text" + # end + # pdf.text "This starts in line with the first line of text" + # + # If no block is provided, operates just like move_right. + # + def indent(n) + if block_given? + move_right n + yield + move_left n + else + move_right n + end + end + # move the cursor to an arbitary position on the current page def move_to(x,y) @context.move_to(x,y) end # 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 + # 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) + bytes == "%%EOF\n" ? true : false + end + # add the same elements to multiple pages. Useful for adding items like headers, footers and # watermarks. # + # There is a single block parameter that is a proxy to the current PDF::Wrapper object that + # disallows start_new_page calls. Every other method from PDF::Wrapper is considered valid. + # # arguments: # <tt>spec</tt>:: Which pages to add the items to. :all, :odd, :even, a range, an Array of numbers or an number # # To add text to every page that mentions the page number - # pdf.repeating_element(:all) do - # pdf.text("Page #{pdf.page}!", :left => pdf.margin_left, :top => pdf.margin_top, :font_size => 18) + # pdf.repeating_element(:all) do |page| + # page.text("Page #{page.page}!", :left => page.margin_left, :top => page.margin_top, :font_size => 18) # end # # To add a circle to the middle of every page - # pdf.repeating_element(:all) do - # pdf.circle(pdf.absolute_x_middle, pdf.absolute_y_middle, 100) + # pdf.repeating_element(:all) do |page| + # page.circle(page.absolute_x_middle, page.absolute_y_middle, 100) # end + # def repeating_element(spec = :all, &block) call_repeating_element(spec, block) # store it so we can add it to future pages @repeating << {:spec => spec, :block => block} @@ -407,10 +488,11 @@ # move to the next page # # options: # <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) @context.show_page @@ -438,24 +520,31 @@ 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 + end + # runs the code in block, passing it a hash of options that might be # required def call_repeating_element(spec, block) - # TODO: disallow start_new_page when adding a repeating element if spec == :all || (spec == :even && (page % 2) == 0) || (spec == :odd && (page % 2) == 1) || (spec.class == Range && spec.include?(page)) || (spec.class == Array && spec.include?(page)) || (spec.respond_to?(:to_i) && spec.to_i == page) @context.save do # add it to the current page - block.call + block.call PDF::Wrapper::Page.new(self) end end end def default_positioning_options @@ -489,23 +578,15 @@ # into a 4 item array. This is normally handled within cairo itself, however when # Cairo and Poppler are both loaded, it breaks. Cairo::Color.parse(c).to_rgb.to_a end - def user_to_device_dist(x,y) - @context.user_to_device_distance(x, y) - end - def user_x_to_device_x(x) @context.user_to_device(x, 0).first.abs end def user_y_to_device_y(y) @context.user_to_device(0, y).last.abs - end - - def device_to_user_dist(x, y) - @context.device_to_user_distance(x, y) end def device_x_to_user_x(x) @context.device_to_user(x, 0).first.abs end