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