lib/pdf/wrapper.rb in pdf-wrapper-0.0.4 vs lib/pdf/wrapper.rb in pdf-wrapper-0.0.5

- old
+ new

@@ -81,19 +81,28 @@ # create a new PDF::Wrapper class to compose a PDF document # 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) + # <tt>:margin_bottom</tt>:: The size of the default bottom margin (default 5% of page) + # <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) def initialize(opts={}) + + # 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" + options = {:paper => :A4, :orientation => :portrait, :background_color => :white } options.merge!(opts) # test for invalid options - options.assert_valid_keys(:paper, :orientation, :background_color) + options.assert_valid_keys(:paper, :orientation, :background_color, :margin_left, :margin_right, :margin_top, :margin_bottom) + options[:paper] = options[:paper].to_sym raise ArgumentError, "Invalid paper option" unless PAGE_SIZES.include?(options[:paper]) # set page dimensions if options[:orientation].eql?(:portrait) @page_width = PAGE_SIZES[options[:paper]][0] @@ -104,15 +113,14 @@ else raise ArgumentError, "Invalid orientation" end # set page margins and dimensions of usable canvas - # TODO: add options for customising the margins. ATM they're always 5% of the page dimensions - @margin_left = (@page_width * 0.05).ceil - @margin_right = (@page_width * 0.05).ceil - @margin_top = (@page_height * 0.05).ceil - @margin_bottom = (@page_height * 0.05).ceil + @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 @surface = Cairo::PDFSurface.new(@output, @page_width, @page_height) @context = Cairo::Context.new(@surface) @@ -263,25 +271,25 @@ # the following options: # # <tt>:border</tt>:: Which sides of the cell should have a border? A string with any combination the letters tblr (top, bottom, left, right). Nil for no border, defaults to all sides. # <tt>:border_width</tt>:: How wide should the border be? # <tt>:border_color</tt>:: What color should the border be? - # <tt>:bgcolor</tt>:: A background color for the cell. Defaults to none. + # <tt>:fill_color</tt>:: A background color for the cell. Defaults to none. # <tt>:padding</tt>:: The number of points to leave between the inside of the border and text. Defaults to 3. def cell(str, x, y, w, h, opts={}) # TODO: add support for pango markup (see http://ruby-gnome2.sourceforge.jp/hiki.cgi?pango-markup) # TODO: add a wrap option so wrapping can be disabled # TODO: handle a single word that is too long for the width # TODO: add an option to draw a border with rounded corners options = default_text_options - options.merge!({:border => "tblr", :border_width => 1, :border_color => :black, :bgcolor => nil, :padding => 3}) + options.merge!({:border => "tblr", :border_width => @default_line_width, :border_color => :black, :fill_color => nil, :padding => 3}) options.merge!(opts) - options.assert_valid_keys(default_text_options.keys + [:width, :border, :border_width, :border_color, :bgcolor, :padding]) + options.assert_valid_keys(default_text_options.keys + [:width, :border, :border_width, :border_color, :fill_color, :padding]) # apply padding - textw = x - (options[:padding] * 2) + textw = w - (options[:padding] * 2) texth = h - (options[:padding] * 2) textx = x + options[:padding] texty = y + options[:padding] options[:border] = "" unless options[:border] @@ -289,25 +297,24 @@ # save the cursor position so we can restore it at the end origx, origy = current_point # TODO: raise an exception if the box coords or dimensions will place it off the canvas - rectangle(x,y,w,h, :color => options[:bgcolor], :fill_color => options[:bgcolor]) if options[:bgcolor] + rectangle(x,y,w,h, :color => options[:fill_color], :fill_color => options[:fill_color]) if options[:fill_color] layout = build_pango_layout(str.to_s, textw, options) set_color(options[:color]) # draw the context on our cairo layout render_layout(layout, textx, texty, texth, :auto_new_page => false) # draw a border around the cell - # TODO: obey options[:border_width] - line(x,y,x+w,y, :color => options[:border_color]) if options[:border].include?("t") - line(x,y+h,x+w,y+h, :color => options[:border_color]) if options[:border].include?("b") - line(x,y,x,y+h, :color => options[:border_color]) if options[:border].include?("l") - line(x+w,y,x+w,y+h, :color => options[:border_color]) if options[:border].include?("r") + line(x,y,x+w,y, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("t") + line(x,y+h,x+w,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("b") + line(x,y,x,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("l") + line(x+w,y,x+w,y+h, :color => options[:border_color], :line_width => options[:border_width]) if options[:border].include?("r") # restore the cursor position move_to(origx, origy) end @@ -419,10 +426,23 @@ width, height = layout.size return height / Pango::SCALE end + # Returns the amount of horizontal space needed to display the supplied text with the requested options + # opts is an options hash that specifies various attributes of the text. See the text function for more information. + # The text is assumed to not wrap. + def text_width(str, opts = {}) + options = default_text_options.merge!(opts) + options.assert_valid_keys(default_text_options.keys) + + layout = build_pango_layout(str.to_s, -1, options) + width, height = layout.size + + return width / Pango::SCALE + end + ##################################################### # Functions relating to working with graphics ##################################################### # draw a circle with radius r and a centre point at (x,y). @@ -482,10 +502,31 @@ # restore the cursor position move_to(origx, origy) end + # Adds a cubic Bezier spline to the path from the (x0, y0) to position (x3, y3) + # in user-space coordinates, using (x1, y1) and (x2, y2) as the control points. + # Options: + # <tt>:color</tt>:: The colour of the line + # <tt>:line_width</tt>:: The width of line. Defaults to 2.0 + def curve(x0, y0, x1, y1, x2, y2, x3, y3, opts = {}) + options = {:color => @default_color, :line_width => @default_line_width } + options.merge!(opts) + options.assert_valid_keys(:color, :line_width) + origx, origy = current_point + + set_color(options[:color]) + @context.set_line_width(options[:line_width]) + move_to(x0,y0) + @context.curve_to(x1, y1, x2, y2, x3, y3).stroke + + # restore the cursor position + move_to(origx, origy) + end + + # draw a rectangle starting at x,y with w,h dimensions. # Parameters: # <tt>:x</tt>:: The x co-ordinate of the top left of the rectangle. # <tt>:y</tt>:: The y co-ordinate of the top left of the rectangle. # <tt>:w</tt>:: The width of the rectangle @@ -628,10 +669,16 @@ ##################################################### # Misc Functions ##################################################### + def pad(n) + x, y = current_point + move_to(x, y + n) + y + n + end + # move the cursor to an arbitary position on the current page def move_to(x,y) raise ArgumentError, 'x cannot be larger than the width of the page' if x > page_width raise ArgumentError, 'y cannot be larger than the height of the page' if y > page_height @context.move_to(x,y) @@ -713,11 +760,15 @@ load_libpango # create a new Pango layout that our text will be added to layout = @context.create_pango_layout layout.text = str.to_s - layout.width = w * Pango::SCALE + if w == -1 + layout.width = -1 + else + layout.width = w * Pango::SCALE + end layout.spacing = options[:spacing] * Pango::SCALE # set the alignment of the text in the layout if options[:alignment].eql?(:left) layout.alignment = Pango::Layout::ALIGN_LEFT @@ -828,11 +879,11 @@ @context.save do @context.translate(opts[:left] || x, opts[:top] || y) @context.scale(width / w, height / h) @context.render_poppler_page(page) end - move_to(x, y + height) + move_to(opts[:left] || x, (opts[:top] || y) + height) end def draw_pixbuf(filename, opts = {}) # based on a similar function in rabbit. Thanks Kou. load_libpixbuf @@ -843,11 +894,11 @@ @context.translate(opts[:left] || x, opts[:top] || y) @context.scale(width / pixbuf.width, height / pixbuf.height) @context.set_source_pixbuf(pixbuf, 0, 0) @context.paint end - move_to(x, y + height) + move_to(opts[:left] || x, (opts[:top] || y) + height) end def draw_png(filename, opts = {}) # based on a similar function in rabbit. Thanks Kou. x, y = current_point @@ -857,11 +908,11 @@ @context.translate(opts[:left] || x, opts[:top] || y) @context.scale(width / img_surface.width, height / img_surface.height) @context.set_source(img_surface, 0, 0) @context.paint end - move_to(x, y + height) + move_to(opts[:left] || x, (opts[:top] || y) + height) end def draw_svg(filename, opts = {}) # based on a similar function in rabbit. Thanks Kou. load_librsvg @@ -872,11 +923,11 @@ @context.translate(opts[:left] || x, opts[:top] || y) @context.scale(width / handle.width, height / handle.height) @context.render_rsvg_handle(handle) #@context.paint end - move_to(x, y + height) + move_to(opts[:left] || x, (opts[:top] || y) + height) end # adds a single table row to the canvas. Top left of the row will be at the current x,y # co-ordinates, so make sure they're set correctly before calling this function # @@ -988,15 +1039,39 @@ # store the starting x and y co-ords. If we start a new page, we'll continue # adding text at the same co-ords orig_x = x orig_y = y - # for each line in the layout - layout.lines.each do |line| - #calculate where the next line starts - ink_rect, logical_rect = line.extents +# layout.alignment = Pango::Layout::ALIGN_RIGHT +# layout.lines.each do |line| +# #calculate where the next line starts +# ink_rect, logical_rect = line.extents +# y = y + (logical_rect.height / Pango::SCALE * (3.0/4.0)) + 1 +# if y >= (orig_y + h) +# # our text is using the maximum amount of vertical space we want it to +# if options[:auto_new_page] +# # create a new page and we can continue adding text +# start_new_page +# x = orig_x +# y = orig_y +# else +# # the user doesn't want us to continue on the next page, so +# # stop adding lines to the canvas +# break +# end +# end +# +# # move to the start of the next line +# move_to(x, y) +# # draw the line on the canvas +# @context.show_pango_layout_line(line) +# end + iter = layout.iter + loop do + line = iter.line + ink_rect, logical_rect = iter.line_extents y = y + (logical_rect.height / Pango::SCALE * (3.0/4.0)) + 1 if y >= (orig_y + h) # our text is using the maximum amount of vertical space we want it to if options[:auto_new_page] # create a new page and we can continue adding text @@ -1009,13 +1084,18 @@ break end end # move to the start of the next line - move_to(x, y) + #move_to(x, y) + baseline = iter.baseline / Pango::SCALE + @context.move_to(x + logical_rect.x / Pango::SCALE, y + baseline) + # draw the line on the canvas @context.show_pango_layout_line(line) - end + + break unless iter.next_line! + end # return the y co-ord we finished on return y end