# 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/cell"
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
#######################################################################
# 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
# The current line thickness
#
def line_width
@line_width || 1
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.
#
def horizontal_line(x1,x2)
line(x1,y,x2,y)
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 given x position from y1 to y2.
#
def vertical_line_at(x,y1,y2)
line(x,y1,x,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
# Sets the fill color. 6 digit HTML color codes are used.
#
# pdf.fill_color "f0ffc1"
#
def fill_color(color=nil)
return @fill_color unless color
@fill_color = color
set_fill_color
end
alias_method :fill_color=, :fill_color
# Sets the line stroking color. 6 digit HTML color codes are used.
#
# pdf.stroke_color "cc2fde"
#
def stroke_color(color=nil)
return @stroke_color unless color
@stroke_color = color
set_stroke_color
end
alias_method :stroke_color=, :stroke_color
# Strokes and closes the current path.
#
def stroke
yield if block_given?
add_content "S"
end
# Fills and closes the current path
#
def fill
yield if block_given?
add_content "f"
end
# Fills, strokes, and closes the current path.
#
def fill_and_stroke
yield if block_given?
add_content "b"
end
# Provides the following shortcuts:
#
# stroke_some_method(*args) #=> some_method(*args); stroke
# fill_some_method(*args) #=> some_method(*args); fill
#
def method_missing(id,*args,&block)
case(id.to_s)
when /^fill_and_stroke_(.*)/
send($1,*args,&block); fill_and_stroke
when /^stroke_(.*)/
send($1,*args,&block); stroke
when /^fill_(.*)/
send($1,*args,&block); fill
else
super
end
end
private
def translate(*point)
x,y = point.flatten
[@bounding_box.absolute_left + x, @bounding_box.absolute_bottom + y]
end
def set_fill_color
r,g,b = [@fill_color[0..1], @fill_color[2..3], @fill_color[4..5]].
map { |e| e.to_i(16) }
add_content "%.3f %.3f %.3f rg" % [r / 255.0, g / 255.0, b / 255.0]
end
def set_stroke_color
r,g,b = [@stroke_color[0..1], @stroke_color[2..3], @stroke_color[4..5]].
map { |e| e.to_i(16) }
add_content "%.3f %.3f %.3f RG" % [r / 255.0, g / 255.0, b / 255.0]
end
def update_colors
@fill_color ||= "000000"
@stroke_color ||= "000000"
set_fill_color
set_stroke_color
end
end
end