# $Id: Graphics.pm,v 1.13 2003/04/29 18:18:05 malay Exp $
# Perl module for Pastel::Graphics
# Author: Malay < curiouser@ccmb.res.in >
# Copyright (c) 2003 by Malay. All rights reserved.
# You may distribute this module under the same terms as perl itself
Pastel::Graphics - Graphics context.
my $graphics = Pastel::Graphics->new(...);
$graphics->show(); #dump the graphics on STDOUT
my $string = $graphics->get_svg(); # get the graphic as string
C<Pastel::Graphics> is the main workhorse of Pastel. It is equivalent to
Java Graphics2D class. Each program first has to create once instance
of this class, then call methods on this and lastly dump the whole
instance onto STDOUT or get it as string.
The class provides the graphics context in that it hold all the
necessary informations for drawing any graphics object correctly.
All styles like fonts, color and strokes are set into this class by
calling appropriate methods. Shapes and Strings are drawn by calling
C<draw()> and C<draw_string()> methods. For a tutorial introduction see
Pastel programming manual.
There are certain shortcut methods that are inbuilt into this
module. You can draw shapes without creating additional
objects. Notable among these classes of methods are C<draw_3D_rect()>
and C<draw_round_rect()> which are not availble from the
L<Pastel::Geometry::Rectangle> class.
package Pastel
::Graphics
;
@ISA = qw( Pastel::Mixin::Mixin); # requires _rearrange() function
my $_width = "800"; #Width of the SVG document
my $_height = "600"; #Height of the SVG document
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20001102//EN" "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd">';
my $class = ref($arg) || $arg;
Creates and returns a new C<Pastel::Graphics> object.
Usage : $graphics = Pastel::Graphics->new(-width=>$w,
Args : $w - Optional. Width of the SVG document in pixels. If not
$h - Optional. Height of the SVG document in pixels. If not
provided the default is 600.
$dtd - Optional. DTD used for SVG document. You can pass your
Returns: Pastel::Graphics object
my ( $self, @args ) = @_;
my ( $w, $h, $dtd, $cgi ) =
$self->_rearrange( [ "WIDTH", "HEIGHT", "DTD", "CGI" ], @args );
$self->{width
} = defined($w) ?
$w : $_width;
$self->{height
} = defined($h) ?
$h : $_height;
$self->{dtd
} = defined($dtd) ?
$dtd : $_dtd;
$self->{CGI
} = defined($cgi) ?
$cgi : "true";
$self->{_temp
} = ""; # To store SVG elements
$self->{_defs
} = ""; # To store DEFS
$self->{_writer
} = $self->_initialize_writer();
Set the font for all the graphics object. Any string drawn using
C<draw_string()> after calling this method will be drawn using the
font presently set using this method. Note that
C<Pastel::Text::AttributedString> objects are immune to font
context. To set the font of C<Pastel::Text::AttributedString> you need
to set the font by setting it as attribute of the C<AttributedString>
Args : $font is a Pastel::Font object.
# if(defined ($arg{FAMILY})){$self->{FONT_FAMILY} = $arg{FAMILY};}
# if(defined ($arg{SIZE})){$self->{FONT_SIZE} = $arg{SIZE};}
# if(defined($arg{COLOR})){$self->{FONT_COLOR} = $arg{COLOR};}
# if(defined($arg{WEIGHT})){$self->{FONT_WEIGHT} = $arg{WEIGHT};}
Sets the color of the graphics context. Any drawing of shapes or text
will be carried out using the color presently set in the graphics
context. The color can be solid color of Pastel::Color object or a
gradient implementing Pastel::GradientI interface.
Usage : set_paint($color)
Args : $color is a Pastel::Color or Pastel::GradientI object.
If the current graphics context has the stroke object set then all
further drawings will be stroked. The stroke color will be the current
color set using C<set_paint()> method. The swith off the stroking you
need to unset the stroke by calling this method without any argument.
Because stroking is done using the current color set in the graphics
context, to draw an object with both stroke and fill where the
stroke-color and the fill-color are different, you need to fill the
object first and set a different color before you draw the object. To
draw a rectangle with blue border and red fill you might do this:
$g = Pastel::Graphics->new();
$g->set_paint(Pastel::Color->red());
$g->set_stroke( Pastel::BasicStroke->new(-width=>5) );
my $rect = Pastel::Geometry::Rectangle(
$g->fill($rect); # filled with red color
$g->set_paint(Pastel::Color->blue());
Usage : set_stroke($stroke) # set the stroke object
set_stroke() # unset stroking
Args : $stroke is a Pastel::BasicStroke object.
#print STDERR "******STROKE called\n";
if ( defined( $self->{stroke
} ) ) {
Returns the current font object in the graphics context.
Returns : Pastel::Font object.
if ( defined( $self->{font
} ) ) {
Returns the current paint object in the graphics context.
Returns : Pastel::Color or object implementing Pastel::GradientI interface.
if ( !defined( $self->{paint
} ) ) {
$self->{paint
} = Pastel
::Color
->black();
Return the current stroke object in the graphics context.
Returns : Pastel::BasicStroke object if stroke is set or undef if not set.
if ( !defined( $self->{stroke
} ) ) {
# $self->{stroke} = Pastel::BasicStroke->new(-width=>1);
# return $self->{stroke};
Shortcut function to draw a rectangle without creating additional
object. The rectangle is stroked with the current C<BasicStroke>
object. If the C<BasicStroke> is not set then the rectangle is created
using an 1 pixel width line. The color of the line is determined by
Usage : draw_rect($x, $y, $width, $height)
Args : $x - X coordinate of the top left corner vertice of rectangle.
$y - Y coordinate of the top left corner vertice of rectangle.
$width - Width of the rectangle.
$height- Height of the rectangle.
my ( $x, $y, $width, $height ) =
$self->_rearrange( [ "X", "Y", "WIDTH", "HEIGHT" ], @_ );
if ( $self->get_paint()->isa("Pastel::Color") ) {
$color = $self->get_paint()->to_svg_stroke();
$color = "stroke:url(" . $self->get_paint()->get_ref() . ');';
$self->add_to_defs( $self->get_paint() );
: Pastel
::BasicStroke
->new();
my $stroke_style = $stroke->to_svg();
my $half_width = $stroke->get_line_width() / 2;
$width = $width - ( 2 * $half_width );
$height = $height - ( 2 * $half_width );
qq(<rect x
="$x" y
="$y" width
="$width" height
="$height" style
="fill:none; $color$stroke_style" />)
Shortcut function to draw a filled rectangle without creating
additional objects. The rectangle is filled with the current paint in
Usage : fill_rect($x, $y, $width, $height);
Args : $x - X coordinate of the top left corner of the rectangle.
$y - Y coordinate of the top left corner of the rectangle.
$width - Width of the rectangle in pixels.
$height- Height of the rectangle in pixels.
# my $color = $self->get_paint()->to_svg_fill();
if ( $self->get_paint()->isa("Pastel::Color") ) {
$color = $self->get_paint()->to_svg_fill();
$color = "fill:url(" . $self->get_paint()->get_ref() . ')';
$self->add_to_defs( $self->get_paint() );
my ( $x, $y, $width, $height ) =
$self->_rearrange( [ "X", "Y", "WIDTH", "HEIGHT" ], @_ );
qq(<rect x
="$x" y
="$y" width
="$width" height
="$height" style
="$color;stroke:none"/>)
Shortcut fuction to draw open rectangle with rounded corners. The
rectangle is stroked with the current stroke if it is set. Otherwise
it is drawn with 1 pixel width line. The color is determined by the
Usage : draw_round_rect($x, $y, $width, $height,
$arc_width, $arc_height);
Args : $x - X coordinate of the top left hand corner.
$y - Y coordinate of the top left hand corner.
$width - Width of the rectangle.
$height - Height of the rectangle.
$arc_width - Horizontal diameter of the arc at the four corners.
$arc_height - vertical diameter of the arc at the four corners.
my ( $self, @args ) = @_;
croak
'Missing parameter in Pastel::Graphics::draw_round_rect()!\n';
#my $paint = $self->get_paint()->to_svg_stroke();
if ( $self->get_paint()->isa("Pastel::Color") ) {
$paint = $self->get_paint()->to_svg_stroke();
$paint = "stroke:url(" . $self->get_paint()->get_ref() . ');';
$self->add_to_defs( $self->get_paint() );
: Pastel
::BasicStroke
->new();
my $half_width = $stroke->get_line_width() / 2;
my $stroke_style = $stroke->to_svg();
my $x = $args[0] + $half_width;
my $y = $args[1] + $half_width;
my $width = $args[2] - ( 2 * $half_width );
my $height = $args[3] - ( 2 * $half_width );
qq(<rect x
="$x" y
="$y" width
="$width" height
="$height" rx
="$args[4]" ry
="$args[4]" style
="fill:none;$paint$stroke_style" />);
=head2 draw_fill_round_rect()
Draws a filled rectangle with rounded corners. The rectangle is filled
with the current paint object in the graphics context.
Usage : $g->draw_fill_round_rect($x, $y, $width, $height,
$arc_width, $arc_height);
Args : $x - X coordinate of the top left hand corner.
$y - Y coordinate of the top left hand corner.
$width - Width of the rectangle.
$height - Height of the rectangle.
$arc_width - Horizontal diameter of the arc at the four corners.
$arc_height - vertical diameter of the arc at the four corners.
sub draw_fill_round_rect
{
my ( $self, @args ) = @_;
croak
'Missing parameter in Pastel::Graphics::draw_fillround_rect()!\n';
# my $paint = $self->get_paint()->to_svg_fill();
if ( $self->get_paint()->isa("Pastel::Color") ) {
$paint = $self->get_paint()->to_svg_fill();
$paint = "fill:url(" . $self->get_paint()->get_ref() . ')';
$self->add_to_defs( $self->get_paint() );
# my $stroke = $self->get_stroke()->to_svg();
qq(<rect x
="$args[0]" y
="$args[1]" width
="$args[2]" height
="$args[3]" rx
="$args[4]" ry
="$args[4]" style
="$paint" />);
Draws a highlighted 3D effect rectangle.
Usage : $g->draw_3D_rect($x, $y, $width, $height, $raised, $bevel);
Args : $x - Top left corner X coordinate of the rectangle.
$y - Top left corner Y coordinate of the rectangle.
$width - Width of the rectangle.
$height - Height of the rectangle.
$raised - Optional. Takes value "true" or "false". Default "true".
$bevel - Optional. Width of the bevel. Default 1.
my ( $self, @args ) = @_;
croak
'Missing parameter in Pastel::Graphics::draw_3D_rect()!\n';
if ( $args[4] && ( $args[4] =~ /true/i ) ) {
my ( $x, $y, $width, $height ) = (@args);
my $before_color = $self->get_paint();
my $current_color = $before_color;
if ( !$before_color->isa("Pastel::Color") ) {
$current_color = Pastel
::Color
->black();
my $brighter = $current_color->brighter();
my $darker = $current_color->darker();
$self->set_paint( $raised ?
$brighter : $darker );
$s .= "M" . $x . " " . $y;
$s .= "L" . eval( $x + $width ) . " " . $y;
$s .= "L" . eval( $x + $width - $bevel ) . " " . eval( $y + $bevel );
$s .= "H" . eval( $x + $bevel );
$s .= "V" . eval( $y + $height - $bevel );
$s .= "L" . $x . " " . eval( $y + $height );
$s .= "style=\"" . $self->get_paint()->to_svg_fill() . "\" />";
$self->set_paint( $raised ?
$darker : $brighter );
$s .= "M" . $x . " " . eval( $y + $height );
$s .= "L" . eval( $x + $bevel ) . " " . eval( $y + $height - $bevel );
$s .= "H" . eval( $x + $width - $bevel );
$s .= "V" . eval( $y + $bevel );
$s .= "L" . eval( $x + $width ) . " " . $y;
$s .= "V" . eval( $y + $height );
$s .= "z" . "\" style=\"" . $self->get_paint()->to_svg_fill() . "\" />";
# $self->fill_rect($x + $stroke_width, $y + $height - $stroke_width,
# $width -$stroke_width, $stroke_width); $self->fill_rect($x +
# $width - $stroke_width, $y , $stroke_width, $height);
$self->set_paint($before_color);
# my $color = $self->get_paint()->to_svg_stroke();
# my $width = $self->get_stroke()->get_line_width();
# my $stroke = $self->get_stroke();
# $self->set_stroke( Pastel::BasicStroke->new( -width => 1 ) );
# $self->set_paint( $self->get_paint()->brighter() );
# $self->draw_line( $args[0], $args[1], $args[0], $args[1] + $args[3] );
# $args[1], $args[0] + $args[2] - 1,
# $self->set_paint( $self->get_paint()->darker() );
# $args[1] + $args[3] - 1
# $self->set_paint($color);
# $self->set_stroke($stroke);
Draws a C<Pastel::Shape> object. The object is stroked using the current
stroke object in the graphics context using the current paint object
in the graphics context. The method works for any object which is a
subclass of C<Pastel::Shape>.
If you subclass Pastel::Shape. The child class must implement
C<get_shape()> method, which should return an instance of the graphics
primitive (classes in C<Pastel::Geometry>).
Args : $shape - an object subclassed from Pastel::Shape. All the
classes in Pastel::Geometry are subclass of Pastel::Shape.
#if (defined ($self->{stroke}) ){
#print "***********Stroke defined********\n";
#my $stroke = $self->{stroke};
#my $stroked_ob = $stroke->create_stroked_shape($ob);
#$data = $stroked_$ob->_draw($self);
if ( $ob->can("get_shape") ) {
$shape = $ob->get_shape();
$data = $shape->_draw($self);
$self->_buffer_add($data);
Draws the string supplied in the co-ordinates specified. Three types
of strings can be drawn using this method. Native perl strings can be
supplied which will be internally converted into suitable format. The
entities are automatically escaped. You can even pass perl unicode
strings directly into the method.
The methods also accepts C<Pastel::String> objects which are nothing
but an XML formatted container for the perl string.
In both these above cases the font in which the string will drawn is
the current font object in the graphics context. The strings will also
be stroked with the current stroke in the graphics context and it will
drawn in the current paint color.
The method also accepts C<Pastel::Text::AttributedString> object. In
Java you pass an C<AttributedCharacterIterator> into this method. In
Pastel you pass the C<AttributedString> object
itself. C<AttributedString> object are immune to current graphics
context. If you want to change the stroke or color of the
C<AttributedString> do so in the directly in the object.
A major way in which C<Pastel> differs from C<Java2D> is that the
coordinates can be arrays of numbers each determining the coordinated
of each character in the string. See SVG specification for more
Usage : draw_string("perl string", $x, $y);
draw_string("perl string", \@x, \@y);
draw_string( $string,$x, $y);
draw_string( $string, \@x, \@y);
Args : The method takes 3 arguments. The first argument can be an
object of type Pastel::String or a pure perl string or
Pastel::Text::AttributedString. The string supplied is converted to
XML form automatically. All character code above 126 is converted to
"&xff;" form. "<", ">", "&" and other XML entities are automatically
The second argument can be a single numerical value
indicating the X cordinate of the first character of the
string or the middle of the total length of the string or the
last character depending on text anchoring. This argument
also be a reference to an array indicating the X coordinates
of successive characters in the string.
The third argument can be a single numerical or a reference
to an array of numericals indicating the Y coordinate of the
baseline of the glyph used for drawing the font.
my ( $self, @args ) = @_;
my ( $string, $x, $y ) = $self->_rearrange( [ "TEXT", "X", "Y" ], @args );
my $data = ""; #Holds the SVG string;
unless ( ( defined($string) ) && ( defined($x) ) && ( defined($y) ) ) {
print 'Error: Graphics::drawstring(): Argument missing', "\n";
if ( $string->isa("Pastel::Text::AttributedString") ) {
#print STDERR "AttributedString supplied\n";
$data = $string->to_svg();
# If $string is a string object calls its draw method
elsif ( ref($string) =~ m/String/ ) {
if ( defined( $self->{font
} ) ) { # If the graphics object has a font
$string->set_font( $self->get_font() );
$data = $string->draw($self);
$data = $string->draw($self);
# If $string is a perl string, create a string object, fontify it
# by passing it to the font object and then call its draw method
my $string = Pastel
::String
->new($string);
if ( defined( $self->{font
} ) ) { # If the graphics object has font
# Set the font object of the String
my $fontified = $string->set_font( $self->{font
} );
$data = $fontified->draw($self);
else { # If the graphics object doesn't have font object set
$data = $string->draw($self); # Just call the draw method
$self->_buffer_add($data); # Add the data to the print buffer
Dumps the graphics object as SVG on STDOUT.
my $writer = $self->{_writer
};
$self->_print_svg_start();
if ( %{ $self->{defs
} } ) {
$writer->print_svg('<defs>');
# for ( my $i = 0 ; $i < @{ $self->{defs} } ; $i++ ) {
# $writer->print_svg( ${ $self->{defs} }[$i]->to_svg() );
$writer->print_svg( $self->_get_defs() );
$writer->print_svg('</defs>');
$writer->print_svg( $self->{_temp
} );
if ( !$shape->isa("Pastel::Shape") ) {
croak
'Error: Only a shape can be filled by Pastel::Graphics->fill()';
$shape->set_fill( $self->get_paint() );
# ${ $self->{defs} }[ @{ $self->{defs} } ] = $object;
# to keep only the unique element we just add the object with an id
my $id = $object->get_id();
$self->{defs
}->{id
} = $object;
if ( %{ $self->{defs
} } ) {
foreach my $key ( keys %{ $self->{defs
} } ) {
$s .= $self->{defs
}->{$key}->to_svg();
my $s = '<?xml version="1.0" encoding="iso-8859-1"?>';
$s .= "\n" . $self->{dtd
} . "\n";
. $self->{height
} . "\">";
if ( %{ $self->{defs
} } ) {
$s .= "\n" . '<defs>' . "\n";
# for ( my $i = 0 ; $i < @{ $self->{defs} } ; $i++ ) {
# $s .= ${ $self->{defs} }[$i]->to_svg();
$s .= $self->_get_defs();
$s .= "\n" . '</defs>' . "\n";
my $s = '<polyline style="fill:none; ';
$s .= $self->get_paint()->to_svg() . " ";
if ( $self->get_stroke() ) {
$s .= $self->get_stroke()->to_svg() . "\" ";
$s .= Pastel
::BasicStroke
()->to_svg() . "\" ";
for ( my $i = 0 ; $i < @x ; $i++ ) {
$s .= $x[$i] . "," . $y[$i];
if ( $i != ( scalar(@x) - 1 ) ) {
# my $s .= '<path style="fill:none; ';
# $s .= $self->get_paint()->to_svg();
# $s .= " ".$self->get_stroke()->to_svg()."\" ";
# $s .= 'd="M'.$x[0].",".$y[0]." ";
# for ( my $i=0; $i < @x; $i++){
# $s .= "L".$x[$i].",".$y[$i];
# if( $i != (scalar(@x) - 1)){
$self->{_temp
} .= $s . "\n";
my ( $self, @args ) = @_;
croak
"Missing parameters in Pastel::Graphics::draw_line()!\n";
my $color = $self->get_paint()->to_svg_stroke();
my $stroke = $self->get_stroke()->to_svg();
qq(<line x1
="$args[0]" y1
="$args[1]" x2
="$args[2]" y2
="$args[3]" style
="fill:none; $color $stroke" />);
my ( $self, @args ) = @_;
croak
"Missing parameters in Pastel::Graphics::draw_line()!\n";
my $color = $self->get_paint()->to_svg_stroke();
my $stroke = $self->get_stroke()->to_svg();
my $cx = $args[0] + ( $args[2] / 2 );
my $cy = $args[1] + ( $args[3] / 2 );
qq(<ellipse cx
="$cx" cy
="$cy" rx
="$rx" ry
="$ry" style
="fill:none; $color $stroke" />);
my ( $self, @args ) = @_;
croak
"Missing parameters in Pastel::Graphics::draw_line()!\n";
my $color = $self->get_paint()->to_svg_fill();
my $stroke = $self->get_stroke()->to_svg();
my $cx = $args[0] + ( $args[2] / 2 );
my $cy = $args[1] + ( $args[3] / 2 );
qq(<ellipse cx
="$cx" cy
="$cy" rx
="$rx" ry
="$ry" style
="$color $stroke" />);
if ( $_[1] ) { $_[0]->{height
} = $_[1]; }
if ( $_[1] ) { $_[0]->{width
} = $_[1]; }
my $writer = Pastel
::Tools
::Writer
->new();
$self->{_writer
}->print_svg('<?xml version="1.0" encoding="iso-8859-1"?>');
$self->{_writer
}->print_svg( $self->{dtd
} );
$self->{_writer
}->print_svg("</svg>");
$self->{_writer
}->print_svg( "<svg width=\""
if ( ( $self->{DEBUG
} =~ /^true$/i ) && ( $self->{CGI
} ne "true" ) ) {
my $fh = $self->{ERROR_HANDLE
};
print $fh "ERROR: $message\n";
Copyright (c) 2003 by Malay <curiouser@ccmb.res.in>. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.