# encoding: utf-8 # graphics.rb : Implements PDF drawing primitives # # Copyright April 2008, Gregory Brown. All Rights Reserved. # # This is free software. Please see the LICENSE and COPYING files for details. require "enumerator" require "prawn/graphics/color" module Prawn # Implements the drawing facilities for Prawn::Document. # Use this to draw the most beautiful imaginable things. # # This file lifts and modifies several of PDF::Writer's graphics functions # ruby-pdf.rubyforge.org # module Graphics include Color ####################################################################### # Low level drawing operations must translate to absolute coords! # ####################################################################### # Moves the drawing position to a given point. The point can be # specified as a tuple or a flattened argument list # # pdf.move_to [100,50] # pdf.move_to(100,50) # def move_to(*point) x,y = translate(point) add_content("%.3f %.3f m" % [ x, y ]) end # Draws a line from the current drawing position to the specified point. # The destination may be described as a tuple or a flattened list: # # pdf.line_to [50,50] # pdf.line_to(50,50) # def line_to(*point) x,y = translate(point) add_content("%.3f %.3f l" % [ x, y ]) end # Draws a Bezier curve from the current drawing position to the # specified point, bounded by two additional points. # # pdf.curve_to [100,100], :bounds => [[90,90],[75,75]] # def curve_to(dest,options={}) options[:bounds] or raise Prawn::Errors::InvalidGraphicsPath, "Bounding points for bezier curve must be specified "+ "as :bounds => [[x1,y1],[x2,y2]]" curve_points = (options[:bounds] << dest).map { |e| translate(e) } add_content("%.3f %.3f %.3f %.3f %.3f %.3f c" % curve_points.flatten ) end # Draws a rectangle given point, width and # height. The rectangle is bounded by its upper-left corner. # # pdf.rectangle [300,300], 100, 200 # def rectangle(point,width,height) x,y = translate(point) add_content("%.3f %.3f %.3f %.3f re" % [ x, y - height, width, height ]) end ########################################################### # Higher level functions: May use relative coords # ########################################################### # Sets line thickness to the width specified. # def line_width=(width) @line_width = width add_content("#{width} w") end # When called without an argument, returns the current line thickness. # When called with an argument, sets the line thickness to the specified # value (in PDF points) # # pdf.line_width #=> 1 # pdf.line_width(5) # pdf.line_width #=> 5 # def line_width(width=nil) if width self.line_width = width else @line_width || 1 end end # Draws a line from one point to another. Points may be specified as # tuples or flattened argument list: # # pdf.line [100,100], [200,250] # pdf.line(100,100,200,250) # def line(*points) x0,y0,x1,y1 = points.flatten move_to(x0, y0) line_to(x1, y1) end # Draws a horizontal line from x1 to x2 at the # current y position, or the position specified by the :at option. # # # draw a line from [25, 75] to [100, 75] # horizontal_line 25, 100, :at => 75 # def horizontal_line(x1,x2,options={}) if options[:at] y1 = options[:at] else y1 = y - bounds.absolute_bottom end line(x1,y1,x2,y1) end # Draws a horizontal line from the left border to the right border of the # bounding box at the current y position. # def horizontal_rule horizontal_line(bounds.left, bounds.right) end # Draws a vertical line at the x cooordinate given by :at from y1 to y2. # # # draw a line from [25, 100] to [25, 300] # vertical_line 100, 300, :at => 25 # def vertical_line(y1,y2,params) line(params[:at],y1,params[:at],y2) end # Draws a Bezier curve between two points, bounded by two additional # points # # pdf.curve [50,100], [100,100], :bounds => [[90,90],[75,75]] # def curve(origin,dest, options={}) move_to(*origin) curve_to(dest,options) end # This constant is used to approximate a symmetrical arc using a cubic # Bezier curve. # KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0) # Draws a circle of radius :radius with the centre-point at point # as a complete subpath. The drawing point will be moved to the # centre-point upon completion of the drawing the circle. # # pdf.circle_at [100,100], :radius => 25 # def circle_at(point, options) x,y = point ellipse_at [x, y], options[:radius] end # Draws an ellipse of +x+ radius r1 and +y+ radius r2 # with the centre-point at point as a complete subpath. The # drawing point will be moved to the centre-point upon completion of the # drawing the ellipse. # # # draws an ellipse with x-radius 25 and y-radius 50 # pdf.ellipse_at [100,100], 25, 50 # def ellipse_at(point, r1, r2 = r1) x, y = point l1 = r1 * KAPPA l2 = r2 * KAPPA move_to(x + r1, y) # Upper right hand corner curve_to [x, y + r2], :bounds => [[x + r1, y + l1], [x + l2, y + r2]] # Upper left hand corner curve_to [x - r1, y], :bounds => [[x - l2, y + r2], [x - r1, y + l1]] # Lower left hand corner curve_to [x, y - r2], :bounds => [[x - r1, y - l1], [x - l2, y - r2]] # Lower right hand corner curve_to [x + r1, y], :bounds => [[x + l2, y - r2], [x + r1, y - l1]] move_to(x, y) end # Draws a polygon from the specified points. # # # draws a snazzy triangle # pdf.polygon [100,100], [100,200], [200,200] # def polygon(*points) move_to points[0] (points << points[0]).each_cons(2) do |p1,p2| line_to(*p2) end end # Strokes and closes the current path. See Graphic::Color for color details # def stroke yield if block_given? add_content "S" end # Draws and strokes a rectangle represented by the current bounding box # def stroke_bounds stroke_rectangle bounds.top_left, bounds.width, bounds.height end # Fills and closes the current path. See Graphic::Color for color details # def fill yield if block_given? add_content "f" end # Fills, strokes, and closes the current path. See Graphic::Color for color details # def fill_and_stroke yield if block_given? add_content "b" end private def translate(*point) x,y = point.flatten [@bounding_box.absolute_left + x, @bounding_box.absolute_bottom + y] end def translate!(point) point.replace(translate(point)) end end end