1package GD::SVG; 2 3use strict; 4use Carp 'croak','carp','confess'; 5use SVG; 6#use warnings; 7use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $AUTOLOAD); 8require Exporter; 9 10$VERSION = '0.33'; 11# $Id: SVG.pm,v 1.16 2009/05/10 14:07:17 todd Exp $ 12 13# Conditional support for side-by-side raster generation. Off for now. 14# Methods that support this are commented out multiple times (ie ######) 15use constant USEGD => 0; 16if (USEGD) { 17 eval "use GD"; 18} 19 20# A global debug flag which can be overriden by new() 21use constant DEBUG => 0; 22 23@ISA = qw(Exporter); 24%EXPORT_TAGS = ('cmp' => [qw(GD_CMP_IMAGE 25 GD_CMP_NUM_COLORS 26 GD_CMP_COLOR 27 GD_CMP_SIZE_X 28 GD_CMP_SIZE_Y 29 GD_CMP_TRANSPARENT 30 GD_CMP_BACKGROUND 31 GD_CMP_INTERLACE 32 GD_CMP_TRUECOLOR 33 ) 34 ] 35 ); 36 37@EXPORT = qw( 38 gdStyled 39 gdBrushed 40 gdTransparent 41 gdTinyFont 42 gdSmallFont 43 gdMediumBoldFont 44 gdLargeFont 45 gdGiantFont 46 gdDashSize 47 gdMaxColors 48 gdStyledBrushed 49 gdTiled 50 gdChord 51 gdEdged 52 gdNoFill 53 gdArc 54 gdPie 55 ); 56 57# Not yet implemented 58#@EXPORT_OK = qw ( 59# GD_CMP_IMAGE 60# GD_CMP_NUM_COLORS 61# GD_CMP_COLOR 62# GD_CMP_SIZE_X 63# GD_CMP_SIZE_Y 64# GD_CMP_TRANSPARENT 65# GD_CMP_BACKGROUND 66# GD_CMP_INTERLACE 67# GD_CMP_TRUECOLOR 68# ); 69 70 71# GD does not allow dynamic creation of fonts. These default values 72# are approximate sizes for the various fonts based on an extensive 73# afternoon of cross-comparison ;) 74use constant DEFAULT_FONT => 'Helvetica'; 75use constant TINY_HEIGHT => 8; 76use constant TINY_WIDTH => 5; 77use constant TINY_WEIGHT => 'normal'; 78use constant SMALL_HEIGHT => 11; # originally 12 79use constant SMALL_WIDTH => 6; 80use constant SMALL_WEIGHT => 'normal'; 81use constant MEDIUM_BOLD_HEIGHT => 13; 82use constant MEDIUM_BOLD_WIDTH => 7; 83use constant MEDIUM_BOLD_WEIGHT => 'bold'; 84use constant LARGE_HEIGHT => 16; 85use constant LARGE_WIDTH => 8; 86use constant LARGE_WEIGHT => 'normal'; 87use constant GIANT_HEIGHT => 15; 88use constant GIANT_WIDTH => 8; 89use constant GIANT_WEIGHT => 'bold'; 90 91# TEXT_KLUDGE controls the number of pixels to bump text on the 92# Y-axis in order to more closely match GD output. 93use constant TEXT_KLUDGE => '2'; 94 95######################### 96# END CONSTANTS - No user serviceable options below this point 97######################### 98 99# Trap GD methods that are not yet implemented in SVG.pm 100sub AUTOLOAD { 101 my $self = shift; 102 warn "GD method $AUTOLOAD is not implemented in GD::SVG" if ref $self && $self->{debug} > 0; 103} 104 105################################################## 106# Exported methods that belong in Main namespace # 107################################################## 108 109# In GD, the gdStyled method allows one to draw with a styled line 110# Here we will simply return the format of the line along with a flag 111# so that appropriate subroutines can deal with it. 112 113# Similarly, the gdTransparent method lets users introduce gaps in 114# lines. I'll handle it similarly to gdStyled... 115# This might just be as simple as setting the color to the background color. 116# (This, of course, will not work for styled lines). 117sub gdStyled { return 'gdStyled'; } 118sub gdBrushed { return 'gdBrushed'; } 119sub gdTransparent { return 'gdTransparent'; } 120 121sub gdStyledBrush { _error('gdStyledBrush'); } 122sub gdTiled { _error('gdTiled'); } 123sub gdDashSize { _error('gdDashSize'); } 124sub gdMaxColors { _error('gdMaxColors'); } 125 126# Bitwise operations for filledArcs 127sub gdArc { return 0; } 128sub gdPie { return 0; } 129sub gdChord { return 1; } 130sub gdEdged { return 4; } 131sub gdNoFill { return 2; } 132 133sub gdAntiAliased { _error('gdAntiAliased'); } 134sub setAntiAliased { shift->_error('setAntiAliased'); } 135sub setAntiAliasedDontBlend { shift->_error('setAntiAliasedDontBlend'); } 136 137################################ 138# Font Factories and Utilities 139################################ 140sub gdTinyFont { 141 my $this = bless {},'GD::SVG::Font'; 142 $this->{font} = DEFAULT_FONT; 143 $this->{height} = TINY_HEIGHT; 144 $this->{width} = TINY_WIDTH; 145 $this->{weight} = TINY_WEIGHT; 146 return $this; 147} 148 149sub gdSmallFont { 150 my $this = bless {},'GD::SVG::Font'; 151 $this->{font} = DEFAULT_FONT; 152 $this->{height} = SMALL_HEIGHT; 153 $this->{width} = SMALL_WIDTH; 154 $this->{weight} = SMALL_WEIGHT; 155 return $this; 156} 157 158sub gdMediumBoldFont { 159 my $this = bless {},'GD::SVG::Font'; 160 $this->{font} = DEFAULT_FONT; 161 $this->{height} = MEDIUM_BOLD_HEIGHT; 162 $this->{width} = MEDIUM_BOLD_WIDTH; 163 $this->{weight} = MEDIUM_BOLD_WEIGHT; 164 return $this; 165} 166 167sub gdLargeFont { 168 my $this = bless {},'GD::SVG::Font'; 169 $this->{font} = DEFAULT_FONT; 170 $this->{height} = LARGE_HEIGHT; 171 $this->{width} = LARGE_WIDTH; 172 $this->{weight} = LARGE_WEIGHT; 173 return $this; 174} 175 176sub gdGiantFont { 177 my $this = bless {},'GD::SVG::Font'; 178 $this->{font} = DEFAULT_FONT; 179 $this->{height} = GIANT_HEIGHT; 180 $this->{width} = GIANT_WIDTH; 181 $this->{weight} = GIANT_WEIGHT; 182 return $this; 183} 184 185# Don't break stuff! 186# The can() method is not supported in GD::SVG 187# sub can { return 0; } 188 189 190package GD::SVG::Image; 191use Carp 'croak','carp','confess'; 192 193# There must be a better way to trap these errors 194sub _error { 195 my ($self,$method) = @_; 196 warn "GD method $method is not implemented in GD::SVG" if ($self->{debug} > 0); 197} 198 199 200######################### 201# GD Constants 202######################### 203# Kludge - use precalculated values of cos(theta) and sin(theta) 204# so that I do no have to examine quadrants 205 206my @cosT = (qw/1024 1023 1023 1022 1021 1020 1018 1016 1014 1011 1008 2071005 1001 997 993 989 984 979 973 968 962 955 949 942 935 928 920 912 208904 895 886 877 868 858 848 838 828 817 806 795 784 772 760 748 736 209724 711 698 685 671 658 644 630 616 601 587 572 557 542 527 512 496 210480 464 448 432 416 400 383 366 350 333 316 299 282 265 247 230 212 211195 177 160 142 124 107 89 71 53 35 17 0 -17 -35 -53 -71 -89 -107 -124 212-142 -160 -177 -195 -212 -230 -247 -265 -282 -299 -316 -333 -350 -366 213-383 -400 -416 -432 -448 -464 -480 -496 -512 -527 -542 -557 -572 -587 214-601 -616 -630 -644 -658 -671 -685 -698 -711 -724 -736 -748 -760 -772 215-784 -795 -806 -817 -828 -838 -848 -858 -868 -877 -886 -895 -904 -912 216-920 -928 -935 -942 -949 -955 -962 -968 -973 -979 -984 -989 -993 -997 217-1001 -1005 -1008 -1011 -1014 -1016 -1018 -1020 -1021 -1022 -1023 218-1023 -1024 -1023 -1023 -1022 -1021 -1020 -1018 -1016 -1014 -1011 219-1008 -1005 -1001 -997 -993 -989 -984 -979 -973 -968 -962 -955 -949 220-942 -935 -928 -920 -912 -904 -895 -886 -877 -868 -858 -848 -838 -828 221-817 -806 -795 -784 -772 -760 -748 -736 -724 -711 -698 -685 -671 -658 222-644 -630 -616 -601 -587 -572 -557 -542 -527 -512 -496 -480 -464 -448 223-432 -416 -400 -383 -366 -350 -333 -316 -299 -282 -265 -247 -230 -212 224-195 -177 -160 -142 -124 -107 -89 -71 -53 -35 -17 0 17 35 53 71 89 107 225124 142 160 177 195 212 230 247 265 282 299 316 333 350 366 383 400 226416 432 448 464 480 496 512 527 542 557 572 587 601 616 630 644 658 227671 685 698 711 724 736 748 760 772 784 795 806 817 828 838 848 858 228868 877 886 895 904 912 920 928 935 942 949 955 962 968 973 979 984 229989 993 997 1001 1005 1008 1011 1014 1016 1018 1020 1021 1022 1023 2301023/); 231 232my @sinT = (qw/0 17 35 53 71 89 107 124 142 160 177 195 212 230 247 233265 282 299 316 333 350 366 383 400 416 432 448 464 480 496 512 527 234542 557 572 587 601 616 630 644 658 671 685 698 711 724 736 748 760 235772 784 795 806 817 828 838 848 858 868 877 886 895 904 912 920 928 236935 942 949 955 962 968 973 979 984 989 993 997 1001 1005 1008 1011 2371014 1016 1018 1020 1021 1022 1023 1023 1024 1023 1023 1022 1021 1020 2381018 1016 1014 1011 1008 1005 1001 997 993 989 984 979 973 968 962 955 239949 942 935 928 920 912 904 895 886 877 868 858 848 838 828 817 806 240795 784 772 760 748 736 724 711 698 685 671 658 644 630 616 601 587 241572 557 542 527 512 496 480 464 448 432 416 400 383 366 350 333 316 242299 282 265 247 230 212 195 177 160 142 124 107 89 71 53 35 17 0 -17 243-35 -53 -71 -89 -107 -124 -142 -160 -177 -195 -212 -230 -247 -265 -282 244-299 -316 -333 -350 -366 -383 -400 -416 -432 -448 -464 -480 -496 -512 245-527 -542 -557 -572 -587 -601 -616 -630 -644 -658 -671 -685 -698 -711 246-724 -736 -748 -760 -772 -784 -795 -806 -817 -828 -838 -848 -858 -868 247-877 -886 -895 -904 -912 -920 -928 -935 -942 -949 -955 -962 -968 -973 248-979 -984 -989 -993 -997 -1001 -1005 -1008 -1011 -1014 -1016 -1018 249-1020 -1021 -1022 -1023 -1023 -1024 -1023 -1023 -1022 -1021 -1020 250-1018 -1016 -1014 -1011 -1008 -1005 -1001 -997 -993 -989 -984 -979 251-973 -968 -962 -955 -949 -942 -935 -928 -920 -912 -904 -895 -886 -877 252-868 -858 -848 -838 -828 -817 -806 -795 -784 -772 -760 -748 -736 -724 253-711 -698 -685 -671 -658 -644 -630 -616 -601 -587 -572 -557 -542 -527 254-512 -496 -480 -464 -448 -432 -416 -400 -383 -366 -350 -333 -316 -299 255-282 -265 -247 -230 -212 -195 -177 -160 -142 -124 -107 -89 -71 -53 -35 256-17 /); 257 258 259############################# 260# GD::SVG::Image methods 261############################# 262sub new { 263 my ($self,$width,$height,$debug) = @_; 264 my $this = bless {},$self; 265 my $img = SVG->new(width=>$width,height=>$height); 266 $this->{img} = [$img]; 267 $this->{width} = $width; 268 $this->{height} = $height; 269 270 # Let's create an internal representation of the image in GD 271 # so that I can easily use some of GD's methods 272 ###GD###$this->{gd} = GD::Image->new($width,$height); 273 274 # Let's just assume that we always want the foreground color to be 275 # black This, for the most part, works for Bio::Graphics. This 276 # certainly needs to be fixed... 277 $this->{foreground} = $this->colorAllocate(0,0,0); 278 $this->{debug} = ($debug) ? $debug : GD::SVG::DEBUG; 279 return $this; 280} 281 282sub img { 283 my $this = shift; 284 return $this->{img}[0]; 285} 286 287sub currentGroup { 288 my $this = shift; 289 $this->{currentGroup} = shift if @_; 290 return $this->{currentGroup} || $this->{img}[-1]; 291 return $this->{img}[-1]; 292} 293 294sub closeAllGroups { 295 my $this = shift; 296 while (@{$this->{img}}>1) { 297 pop @{$this->{img}}; 298 } 299} 300 301 302############################# 303# Image Data Output Methods # 304############################# 305sub svg { 306 my $self = shift; 307 $self->closeAllGroups; 308 my $img = $self->img; 309 $img->xmlify(-pubid => "-//W3C//DTD SVG 1.0//EN", 310 -inline => 1); 311} 312 313###GD###sub png { 314###GD### my ($self,$compression) = @_; 315###GD### return $self->{gd}->png($compression); 316###GD###} 317 318###GD###sub jpeg { 319###GD### my ($self,$quality) = @_; 320###GD### return $self->{gd}->jpeg($quality); 321###GD###} 322 323############################# 324# Color management routines # 325############################# 326# As with GD, colorAllocate returns integers... 327# This could easily rely on GD itself to generate the indices 328sub colorAllocate { 329 my ($self,$r,$g,$b,$alpha) = @_; 330 $r ||= 0; 331 $g ||= 0; 332 $b ||= 0; 333 $alpha ||= 0; 334 335 ###GD###my $newindex = $self->{gd}->colorAllocate($r,$g,$b); 336 337 # Cannot use the numberof keys to generate index 338 # colorDeallocate removes keys. 339 # Instead use the colors_added array. 340 my $new_index = (defined $self->{colors_added}) ? scalar @{$self->{colors_added}} : 0; 341 $self->{colors}->{$new_index} = [$r,$g,$b,$alpha]; 342 343 # Keep a list of colors in the order that they are added 344 # This is used as a kludge for setBrush 345 push (@{$self->{colors_added}},$new_index); 346 return $new_index; 347} 348 349sub colorAllocateAlpha { 350 my $self = shift; 351 ###GD###$self->{gd}->colorAllocateAlpha($r,$g,$b,$alpha); 352 $self->colorAllocate(@_); 353} 354 355sub colorDeallocate { 356 my ($self,$index) = @_; 357 my $colors = %{$self->{colors}}; 358 delete $colors->{$index}; 359 ###GD###$self->{gd}->colorDeallocate($index); 360} 361 362# workaround for bad GD 363sub colorClosest { 364 my ($self,@c) = @_; 365 ###GD###my $index = $self->{gd}->colorClosest(@c); 366 367 # Let's just return the color for now. 368 # Terrible kludge. 369 my $index = $self->colorAllocate(@c); 370 return $index; 371 # my ($self,$gd,@c) = @_; 372 # return $self->{closestcache}{"@c"} if exists $self->{closestcache}{"@c"}; 373 # return $self->{closestcache}{"@c"} = $gd->colorClosest(@c) if $GD::VERSION < 2.04; 374 # my ($value,$index); 375 # for (keys %COLORS) { 376 # my ($r,$g,$b) = @{$COLORS{$_}}; 377 # my $dist = ($r-$c[0])**2 + ($g-$c[1])**2 + ($b-$c[2])**2; 378 # ($value,$index) = ($dist,$_) if !defined($value) || $dist < $value; 379 # } 380 # return $self->{closestcache}{"@c"} = $self->{translations}{$index}; 381} 382 383sub colorClosestHWB { shift->_error('colorClosestHWB'); } 384 385sub colorExact { 386 my ($self,$r,$g,$b) = @_; 387 ###GD###my $index = $self->{gd}->colorExact($r,$g,$b); 388 389 # Let's just allocate the color instead of looking it up 390 my $index = $self->colorAllocate($r,$g,$b); 391 if ($index) { 392 return $index; 393 } else { 394 return ('-1'); 395 } 396} 397 398sub colorResolve { 399 my ($self,$r,$g,$b) = @_; 400 ###GD###my $index = $self->{gd}->colorResolve($r,$g,$b); 401 my $index = $self->colorAllocate($r,$g,$b); 402 return $index; 403} 404 405sub colorsTotal { 406 my $self = shift; 407 ###GD###return $self->{gd}->colorsTotal; 408 return scalar keys %{$self->{colors}}; 409} 410 411 412sub getPixel { 413 my ($self,$x,$y) = @_; 414 # Internal GD - probably unnecessary in this context... 415 # Will contstruct appropriate return value later 416 ###GD### $self->{gd}->getPixel($x,$y); 417 418 # I don't have any cogent way to fetch the value of an asigned pixel 419 # without calculating all positions and loading into memory. 420 421 # For these purposes, I could maybe just look it up... From a hash 422 # table or something - Keep track of all assigned pixels and their 423 # color. Ugh. Compute intensive. 424 return (1); 425} 426 427# Given the color index, return its rgb triplet 428sub rgb { 429 my ($self,$index) = @_; 430 my ($r,$g,$b) = @{$self->{colors}->{$index}}; 431 return ($r,$g,$b); 432} 433 434sub transparent { shift->_error('transparent'); } 435 436 437####################### 438# Special Colors 439####################### 440# Kludgy preliminary support for gdBrushed This is based on 441# Bio::Graphics implementation of set_pen which in essence just 442# controls line color and thickness... We will assume that the last 443# color added is intended to be the foreground color. 444sub setBrush { 445 my ($self,$pen) = @_; 446 ###GD###$self->{gd}->setBrush($pen); 447 my ($width,$height) = $pen->getBounds(); 448 my $last_color = $pen->{colors_added}->[-1]; 449 my ($r,$g,$b) = $self->rgb($last_color); 450 $self->{gdBrushed}->{color} = $self->colorAllocate($r,$g,$b); 451 $self->{gdBrushed}->{thickness} = $width; 452} 453 454# There is no direct translation of gdStyled. In gd, this is used to 455# set the style for the line using the settings of the current brush. 456# Drawing with the new style is then used by passing the gdStyled as a 457# color. 458sub setStyle { 459 my ($self,@colors) = @_; 460 ###GD###$self->{gd}->setStyle(@colors); 461 $self->{gdStyled}->{color} = [ @colors ]; 462 return; 463} 464 465# Lines in GD are 1 pixel in diameter by default. 466# setThickness allows line thickness to be changed. 467# This should be retained until it's changed again 468# Each method should check the thickness of the line... 469sub setThickness { 470 my ($self,$thickness) = @_; 471 ###GD### $self->{gd}->setThickness($thickness); 472 $self->{line_thickness} = $thickness; 473 # WRONG! 474 # $self->{prev_line_thickness} = (!defined $self->{prev_line_thickness}) ? $thickness : undef; 475} 476 477 478######################## 479# Grouping subroutines # 480######################## 481sub startGroup { 482 my $this = shift; 483 my $id = shift; 484 my $style = shift; 485 486 my @args; 487 push @args,(id => $id) if defined $id; 488 push @args,(style => $style) if defined $style; 489 490 my $group = $this->currentGroup->group(@args); 491 push @{$this->{img}},$group; 492 return $group; 493} 494sub endGroup { 495 my $this = shift; 496 my $group = shift; 497 498 if ($group) { 499 my @imgs = grep {$_ ne $group} @{$this->{img}}; 500 $this->{img} = \@imgs; 501 } 502 elsif (@{$this->{img}}>1) { 503 pop @{$this->{img}}; 504 } 505 delete $this->{currentGroup}; 506} 507sub newGroup { 508 my $this = shift; 509 my $group = $this->startGroup(@_); 510 eval "require GD::Group" unless GD::Group->can('new'); 511 return GD::Group->new($this,$group); 512} 513 514####################### 515# Drawing subroutines # 516####################### 517sub setPixel { 518 my ($self,$x1,$y1,$color_index) = @_; 519 ###GD### $self->{gd}->setPixel($x1,$y1,$color_index); 520 my ($img,$id,$thickness,$dasharray) = $self->_prep($x1,$y1); 521 my $color = $self->_get_color($color_index); 522 my $result = 523 $img->circle(cx=>$x1,cy=>$y1,r=>'0.03', 524 id=>$id, 525 style=>{ 526 'stroke'=>$color, 527 'fill' =>$color, 528 'fill-opacity'=>'1.0' 529 } 530 ); 531 return $result; 532} 533 534sub line { 535 my ($self,$x1,$y1,$x2,$y2,$color_index) = @_; 536 # Are we trying to draw with a styled line (ie gdStyled, gdBrushed?) 537 # If so, we need to deconstruct the values for line thickness, 538 # foreground color, and dash spacing 539 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 540 my $fg = $self->_distill_gdSpecial($color_index); 541 $self->line($x1,$y1,$x2,$y2,$fg); 542 } else { 543 ###GD### $self->{gd}->line($x1,$y1,$x2,$y2,$color_index); 544 my ($img,$id) = $self->_prep($x1,$y1); 545 my $style = $self->_build_style($id,$color_index,$color_index); 546 547 # Suggested patch by Jettero to fix lines 548 # that don't go to the ends of their length. 549 # This could possibly be relocated to _build_style 550 # but I'm unsure of the ramifications on other features. 551 $style->{'stroke-linecap'} = 'square'; 552 my $result = $img->line(x1=>$x1,y1=>$y1, 553 x2=>$x2,y2=>$y2, 554 id=>$id, 555 style => $style, 556 ); 557 $self->_reset(); 558 return $result; 559 } 560} 561 562sub dashedLine { shift->_error('dashedLine'); } 563 564# The fill parameter is used internally as a simplification... 565sub rectangle { 566 my ($self,$x1,$y1,$x2,$y2,$color_index,$fill) = @_; 567 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 568 my $fg = $self->_distill_gdSpecial($color_index); 569 $self->rectangle($x1,$y1,$x2,$y2,$fg,$fill); 570 } else { 571 ###GD###$self->{gd}->rectangle($x1,$y1,$x2,$y2,$color_index); 572 my ($img,$id) = $self->_prep($x1,$y1); 573 my $style = $self->_build_style($id,$color_index,$fill); 574 575 # flip coordinates if they are "backwards" 576 ($x1,$x2) = ($x2,$x1) if $x1 > $x2; 577 ($y1,$y2) = ($y2,$y1) if $y1 > $y2; 578 my $result = 579 $img->rectangle(x=>$x1,y=>$y1, 580 width =>$x2-$x1, 581 height =>$y2-$y1, 582 id =>$id, 583 style => $style, 584 ); 585 $self->_reset(); 586 return $result; 587 } 588} 589 590# This should just call the rectangle method passing it a flag. 591# I will need to fix the glyph that bypasses this option... 592sub filledRectangle { 593 my ($self,$x1,$y1,$x2,$y2,$color) = @_; 594 # Call the rectangle method passing the fill color 595 $self->rectangle($x1,$y1,$x2,$y2,$color,$color); 596} 597 598sub polygon { 599 my ($self,$poly,$color,$fill) = @_; 600 $self->_polygon($poly,$color,$fill,1); 601} 602 603sub polyline { 604 my ($self,$poly,$color,$fill) = @_; 605 $self->_polygon($poly,$color,$fill,0); 606} 607 608sub polydraw { 609 my $self = shift; # the GD::Image 610 my $p = shift; # the GD::Polyline or GD::Polygon 611 my $c = shift; # the color 612 return $self->polyline($p, $c) if $p->isa('GD::Polyline'); 613 return $self->polygon($p, $c); 614} 615 616sub _polygon { 617 my ($self,$poly,$color_index,$fill,$close) = @_; 618 my $shape = $close ? 'polygon' : 'polyline'; 619 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 620 my $fg = $self->_distill_gdSpecial($color_index); 621 $self->$shape($poly,$fg); 622 } else { 623 ###GD###$self->{gd}->polygon($poly,$color); 624 # Create seperate x and y arrays of vertices 625 my (@xpoints,@ypoints); 626 if ($poly->can('_fetch_vertices')) { 627 @xpoints = $poly->_fetch_vertices('x'); 628 @ypoints = $poly->_fetch_vertices('y'); 629 } else { 630 my @points = $poly->vertices; 631 @xpoints = map { $_->[0] } @points; 632 @ypoints = map { $_->[1] } @points; 633 } 634 my ($img,$id) = $self->_prep($xpoints[0],$ypoints[0]); 635 my $points = $img->get_path( 636 x=>\@xpoints, y=>\@ypoints, 637 -type=>$shape, 638 ); 639 my $style = $self->_build_style($id,$color_index,$fill); 640 my $result = 641 $img->$shape( 642 %$points, 643 id=>$id, 644 style => $style, 645 ); 646 $self->_reset(); 647 return $result; 648 } 649} 650 651# Passing the stroke doesn't really work as expected... 652sub filledPolygon { 653 my ($self,$poly,$color) = @_; 654 my $result = $self->polygon($poly,$color,$color); 655 return $result; 656} 657 658sub ellipse { 659 my ($self,$x1,$y1,$width,$height,$color_index,$fill) = @_; 660 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 661 my $fg = $self->_distill_gdSpecial($color_index); 662 $self->ellipse($x1,$y1,$width,$height,$fg); 663 } else { 664 ###GD### $self->{gd}->ellipse($x1,$y1,$width,$height,$color_index); 665 666 my ($img,$id) = $self->_prep($x1,$y1); 667 # GD uses width and height - SVG uses radii... 668 $width = $width / 2; 669 $height = $height / 2; 670 my $style = $self->_build_style($id,$color_index,$fill); 671 my $result = 672 $img->ellipse( 673 cx=>$x1, cy=>$y1, 674 rx=>$width, ry=>$height, 675 id=>$id, 676 style => $style, 677 ); 678 $self->_reset(); 679 return $result; 680 } 681} 682 683sub filledEllipse { 684 my ($self,$x1,$y1,$width,$height,$color) = @_; 685 my $result = $self->ellipse($x1,$y1,$width,$height,$color,$color); 686 return $result; 687} 688 689# GD uses the arc() and filledArc() methods in two capacities 690# 1. to create closed ellipses, where start and end are 0 and 360 691# 2. to create honest-to-god open arcs 692# The arc method is no longer being used to draw filledArcs. 693# All the fill-specific code within is no deprecated. 694sub arc { 695 my ($self,$cx,$cy,$width,$height,$start,$end,$color_index,$fill) = @_; 696 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 697 my $fg = $self->_distill_gdSpecial($color_index); 698 $self->arc($cx,$cy,$width,$height,$start,$end,$fg); 699 } else { 700 ###GD### $self->{gd}->arc($x,$y,$width,$height,$start,$end,$color); 701 # Are we just trying to draw a closed arc (an ellipse)? 702 my $result; 703 if ($start == 0 && $end == 360 || $end == 360 && $start == 0) { 704 $result = $self->ellipse($cx,$cy,$width,$height,$color_index,$fill); 705 } else { 706 my ($img,$id) = $self->_prep($cy,$cx); 707 708 # Taking a stab at drawing elliptical arcs 709 my ($start,$end,$large,$sweep,$a,$b) = _calculate_arc_params($start,$end,$width,$height); 710 my ($startx,$starty) = _calculate_point_coords($cx,$cy,$width,$height,$start); 711 my ($endx,$endy) = _calculate_point_coords($cx,$cy,$width,$height,$end); 712 713 # M = move to (origin of the curve) 714 # my $rotation = abs $start - $end; 715 my $style = $self->_build_style($id,$color_index,$fill); 716 $result = 717 $img->path('d'=>"M$startx,$starty " . 718 "A$a,$b 0 $large,$sweep $endx,$endy", 719 style => $style, 720 ); 721 } 722 $self->_reset(); 723 return $result; 724 } 725} 726 727# Return the x and y positions of start and stop of arcs. 728sub _calculate_point_coords { 729 my ($cx,$cy,$width,$height,$angle) = @_; 730 my $x = ( $cosT[$angle % 360] * $width) / (2 * 1024) + $cx; 731 my $y = ( $sinT[$angle % 360] * $height) / (2 * 1024) + $cy; 732 return ($x,$y); 733} 734 735sub _calculate_arc_params { 736 my ($start,$end,$width,$height) = @_; 737 738 # GD uses diameters, SVG uses radii 739 my $a = $width / 2; 740 my $b = $height / 2; 741 742 while ($start < 0 ) { $start += 360; } 743 while ($end < 0 ) { $end += 360; } 744 while ($end < $start ) { $end += 360; } 745 746 my $large = (abs $start - $end > 180) ? 1 : 0; 747 # my $sweep = ($start > $end) ? 0 : 1; # directionality of the arc, + CW, - CCW 748 my $sweep = 1; # Always CW with GD 749 return ($start,$end,$large,$sweep,$a,$b); 750} 751 752sub filledArc { 753 my ($self,$cx,$cy,$width,$height,$start,$end,$color_index,$fill_style) = @_; 754 if ($color_index eq 'gdStyled' || $color_index eq 'gdBrushed') { 755 my $fg = $self->_distill_gdSpecial($color_index); 756 $self->filledArc($cx,$cy,$width,$height,$start,$end,$fg); 757 } else { 758 ###GD### $self->{gd}->arc($x,$y,$width,$height,$start,$end,$color_index); 759 my $result; 760 761 # distill the special colors, if provided... 762 my $fill_color; 763 # Set it to gdArc, the default value to avoid undef errors in comparisons 764 $fill_style ||= 0; 765 if ($fill_style == 2 || $fill_style == 4 || $fill_style == 6) { 766 $fill_color = 'none'; 767 } else { 768 $fill_color = $self->_get_color($color_index); 769 } 770 771 # Are we just trying to draw a closed filled arc (an ellipse)? 772 if (($start == 0 && $end == 360) || ($start == 360 && $end == 0)) { 773 $result = $self->ellipse($cx,$cy,$width,$height,$color_index,$fill_color); 774 } 775 776 # are we trying to draw a pie? 777 elsif ($end - $start > 180 && ($fill_style == 0 || $fill_style == 4)) { 778 $self->filledArc($cx,$cy,$width,$height,$start,$start+180,$color_index,$fill_style); 779# $self->filledArc($cx,$cy,$width,$height,$start+180,$end,$color_index,$fill_style); 780 $result = $self->filledArc($cx,$cy,$width,$height,$start+180,$end,$color_index,$fill_style); 781 } 782 783 else { 784 my ($img,$id) = $self->_prep($cy,$cx); 785 786 my ($start,$end,$large,$sweep,$a,$b) = _calculate_arc_params($start,$end,$width,$height); 787 my ($startx,$starty) = _calculate_point_coords($cx,$cy,$width,$height,$start); 788 my ($endx,$endy) = _calculate_point_coords($cx,$cy,$width,$height,$end); 789 790 # Evaluate the various fill styles 791 # gdEdged connects the center to the start and end 792 if ($fill_style == 4 || $fill_style == 6) { 793 $self->line($cx,$cy,$startx,$starty,$color_index); 794 $self->line($cx,$cy,$endx,$endy,$color_index); 795 } 796 797 # gdNoFill outlines portions of the arc 798 # noFill or gdArc|gdNoFill 799 if ($fill_style == 2 || $fill_style == 6) { 800 $result = $self->arc($cx,$cy,$width,$height,$start,$end,$color_index); 801 return $result; 802 } 803 804 # gdChord|gdNofFill 805 if ($fill_style == 3) { 806 $result = $self->line($startx,$starty,$endx,$endy,$color_index); 807 return $result; 808 } 809 810 # Create the actual filled portion of the arc 811 # This is the default behavior for gdArc and if no style is passed. 812 if ($fill_style == 0 || $fill_style == 4) { 813 # M = move to (origin of the curve) 814 # my $rotation = abs $start - $end; 815 my $style = $self->_build_style($id,$color_index,$fill_color); 816 $result = 817 $img->path('d'=>"M$startx,$starty " . 818 "A$a,$b 0 $large,$sweep $endx,$endy", 819 style => $style, 820 ); 821 } 822 823 # If we are filling, draw a filled triangle to complete. 824 # This is also the same as using gdChord by itself 825 my $poly = GD::SVG::Polygon->new(); 826 $poly->addPt($cx,$cy); 827 $poly->addPt($startx,$starty); 828 $poly->addPt($endx,$endy); 829 $self->filledPolygon($poly,$color_index); 830 } 831 832 $self->_reset(); 833 return $result; 834 } 835} 836 837# Flood fill that stops at first pixel of a different color. 838sub fill { shift->_error('fill'); } 839sub fillToBorder { shift->_error('fillToBorder'); } 840 841################################################## 842# Image Copying Methods 843################################################## 844 845# Taking a stab at implementing the copy() methods 846# Should be relatively easy to implement clone() from this 847sub copy { 848 my $self = shift; 849 my ($source,$dstx,$dsty,$srcx,$srcy,$width,$height) = @_; 850 851 # special case -- if we have been asked to copy a 852 # GD::Image into us, then we embed an image with the 853 # data:url 854 if ($source->isa('GD::Image') || $source->isa('GD::Simple')) { 855 return $self->_copy_image(@_); 856 } 857 858 my $topx = $srcx; 859 my $topy = $srcy; 860 my $bottomx = $srcx + $width; # arithmetic right here? 861 my $bottomy = $srcy + $height; 862 863 # Fetch all elements of the source image 864 my @elements = $source->img->getElements; 865 foreach my $element (@elements) { 866 my $att = $element->getAttributes(); 867 # Points|rectangles|text, circles|ellipses, lines 868 my $x = $att->{x} || $att->{cx} || $att->{x1}; 869 my $y = $att->{y} || $att->{cy} || $att->{y1}; 870 871 # Use the first point for polygons 872 unless ($x && $y) { 873 my @points = split(/\s/,$att->{points}); 874 if (@points) { 875 ($x,$y) = split(',',$points[0]); 876 } 877 } 878 879 # Paths 880 unless ($x && $y) { 881 my @d = split(/\s/,$att->{d}); 882 if (@d) { 883 ($x,$y) = split(',',$d[0]); 884 $x =~ s/^M//; # Remove the style directive 885 } 886 } 887 888 # Are the starting coords within the bounds of the desired rectangle? 889 # We will simplistically assume that the entire glyph fits inside 890 # the rectangle which may not be true. 891 if (($x >= $topx && $y >= $topy) && 892 ($x <= $bottomx && $y <= $bottomy)) { 893 my $type = $element->getType; 894 # warn "$type $x $y $bottomx $bottomy $topx $topy"; 895 896 # Transform the coordinates as necessary, 897 # calculating the offsets relative to the 898 # original bounding rectangle in the source image 899 900 # Text or rectangles 901 if ($type eq 'text' || $type eq 'rect') { 902 my ($newx,$newy) = _transform_coords($topx,$topy,$x,$y,$dstx,$dsty); 903 $element->setAttribute('x',$newx); 904 $element->setAttribute('y',$newy); 905 # Circles or ellipses 906 } elsif ($type eq 'circle' || $type eq 'ellipse') { 907 my ($newx,$newy) = _transform_coords($topx,$topy,$x,$y,$dstx,$dsty); 908 $element->setAttribute('cx',$newx); 909 $element->setAttribute('cy',$newy); 910 # Lines 911 } elsif ($type eq 'line') { 912 my ($newx1,$newy1) = _transform_coords($topx,$topy,$x,$y,$dstx,$dsty); 913 my ($newx2,$newy2) = _transform_coords($topx,$topy,$att->{x2},$element->{y2},$dstx,$dsty); 914 $element->setAttribute('x1',$newx1); 915 $element->setAttribute('y1',$newy1); 916 $element->setAttribute('x2',$newx2); 917 $element->setAttribute('y2',$newy2); 918 # Polygons 919 } elsif ($type eq 'polygon') { 920 my @points = split(/\s/,$att->{points}); 921 my @transformed; 922 foreach (@points) { 923 ($x,$y) = split(',',$_); 924 my ($newx,$newy) = _transform_coords($topx,$topy,$x,$y,$dstx,$dsty); 925 push (@transformed,"$newx,$newy"); 926 } 927 my $transformed = join(" ",@transformed); 928 $element->setAttribute('points',$transformed); 929 # Paths 930 } elsif ($type eq 'path') { 931 932 } 933 934 # Create new elements for the destination image 935 # via the generic SVG::Element::tag method 936 my %attributes = $element->getAttributes; 937 $self->img->tag($type,%attributes); 938 } 939 } 940} 941 942# Used internally by the copy method 943# Transform coordinates of a given point with reference 944# to a bounding rectangle 945sub _transform_coords { 946 my ($refx,$refy,$x,$y,$dstx,$dsty) = @_; 947 my $xoffset = $x - $refx; 948 my $yoffset = $y - $refy; 949 my $newx = $dstx + $xoffset; 950 my $newy = $dsty + $yoffset; 951 return ($newx,$newy); 952} 953 954sub _copy_image { 955 my $self = shift; 956 my ($source,$dstx,$dsty,$srcx,$srcy,$width,$height) = @_; 957 958 eval "use MIME::Base64; 1" 959 or croak "The MIME::Base64 module is required to copy a GD::Image into a GD::SVG: $@"; 960 961 my $subimage = GD::Image->new($width,$height); # will be loaded 962 $subimage->copy($source->isa('GD::Simple') ? $source->gd : $source, 963 0,0, 964 $srcx,$srcy, 965 $width,$height); 966 967 my $data = encode_base64($subimage->png); 968 my ($img,$id) = $self->_prep($dstx,$dsty); 969 my $result = 970 $img->image('x' => $dstx, 971 'y' => $dsty, 972 width => $width, 973 height => $height, 974 id => $id, 975 'xlink:href' => "data:image/png;base64,$data"); 976 $self->_reset; 977 return $result; 978} 979 980 981 982 983################################################## 984# Image Transformation Methods 985################################################## 986 987# None implemented 988 989################################################## 990# Character And String Drawing 991################################################## 992sub string { 993 my ($self,$font_obj,$x,$y,$text,$color_index) = @_; 994 my $img = $self->currentGroup; 995 my $id = $self->_create_id($x,$y); 996 my $formatting = $font_obj->formatting(); 997 my $color = $self->_get_color($color_index); 998 my $result = 999 $img->text( 1000 id=>$id, 1001 x=>$x, 1002 y=>$y + $font_obj->{height} - GD::SVG::TEXT_KLUDGE, 1003 %$formatting, 1004 fill => $color, 1005 )->cdata($text); 1006 return $result; 1007} 1008 1009sub stringUp { 1010 my ($self,$font_obj,$x,$y,$text,$color_index) = @_; 1011 my $img = $self->currentGroup; 1012 my $id = $self->_create_id($x,$y); 1013 my $formatting = $font_obj->formatting(); 1014 my $color = $self->_get_color($color_index); 1015 $x += $font_obj->height; 1016 my $result = 1017 $img->text( 1018 id=>$id, 1019 %$formatting, 1020 'transform' => "translate($x,$y) rotate(-90)", 1021 fill => $color, 1022 )->cdata($text); 1023} 1024 1025sub char { 1026 my ($self,@rest) = @_; 1027 $self->string(@rest); 1028} 1029 1030sub charUp { 1031 my ($self,@rest) = @_; 1032 $self->stringUp(@rest); 1033} 1034 1035# Replicating the TrueType handling 1036#sub GD::Image::stringFT { shift->_error('stringFT'); } 1037 1038sub stringFT { 1039 return; 1040} 1041 1042# not implemented 1043sub useFontConfig { 1044 return 0; 1045} 1046 1047 1048################################################## 1049# Alpha Channels 1050################################################## 1051sub alphaBlending { shift->_error('alphaBlending'); } 1052sub saveAlpha { shift->_error('saveAlpha'); } 1053 1054################################################## 1055# Miscellaneous Image Methods 1056################################################## 1057sub interlaced { shift->_error('inerlaced'); } 1058 1059sub getBounds { 1060 my $self = shift; 1061 my $width = $self->{width}; 1062 my $height = $self->{height}; 1063 return($width,$height); 1064} 1065 1066sub isTrueColor { shift->_error('isTrueColor'); } 1067sub compare { shift->_error('compare'); } 1068sub clip { shift->_error('clip'); } 1069sub boundsSafe { shift->_error('boundsSafe'); } 1070 1071########################################## 1072# Internal routines for meshing with SVG # 1073########################################## 1074# Fetch out typical params used for drawing. 1075package GD::SVG::Image; 1076use Carp 'confess'; 1077 1078sub _prep { 1079 my ($self,@params) = @_; 1080 my $img = $self->currentGroup; 1081 my $id = $self->_create_id(@params); 1082 # my $thickness = $self->_get_thickness() || 1; 1083# return ($img,$id,$thickness,undef); 1084 return ($img,$id,undef,undef); 1085} 1086 1087# Pass in a ordered list to create a hash ref of style parameters 1088# ORDER: $id,$color_index,$fill_color,$stroke_opacity); 1089sub _build_style { 1090 my ($self,$id,$color,$fill,$stroke_opacity) = @_; 1091 my $thickness = $self->_get_thickness() || 1; 1092 1093 my $fill_opacity = ($fill) ? '1.0' : 0; 1094 $fill = defined $fill ? $self->_get_color($fill) : 'none'; 1095 if ((my $color_opacity = $self->_get_opacity($color)) > 0) { 1096 $stroke_opacity = (127-$color_opacity)/127; 1097 } else { 1098 $stroke_opacity ||= '1.0'; 1099 } 1100 my %style = ('stroke' => $self->_get_color($color), 1101 'stroke-opacity' => $stroke_opacity, 1102 'stroke-width' => $thickness, 1103 'fill' => $fill, 1104 'fill-opacity' => $stroke_opacity, 1105 ); 1106 my $dasharray = $self->{dasharray}; 1107 if ($self->{dasharray}) { 1108 $style{'stroke-dasharray'} = @{$self->{dasharray}}; 1109 $style{fill} = 'none'; 1110 } 1111 return \%style; 1112} 1113 1114# From a color index, return a stringified rgb triplet for SVG 1115sub _get_color { 1116 my ($self,$index) = @_; 1117 confess "somebody gave me a bum index!" unless length $index > 0; 1118 return ($index) if ($index =~ /rgb/); # Already allocated. 1119 return ($index) if ($index eq 'none'); # Generate by callbacks using none for fill 1120 my ($r,$g,$b,$a) = @{$self->{colors}->{$index}}; 1121 my $color = "rgb($r,$g,$b)"; 1122 return $color; 1123} 1124 1125sub _get_opacity { 1126 my ($self,$index) = @_; 1127 confess "somebody gave me a bum index!" unless length $index > 0; 1128 return ($index) if ($index =~ /rgb/); # Already allocated. 1129 return ($index) if ($index eq 'none'); # Generate by callbacks using none for fill 1130 my ($r,$g,$b,$a) = @{$self->{colors}->{$index}}; 1131 return $a; 1132} 1133 1134sub _create_id { 1135 my ($self,$x,$y) = @_; 1136 $self->{id_count}++; 1137 return (join('-',$self->{id_count},$x,$y)); 1138} 1139 1140# Break apart the internal representation of gdBrushed 1141# setting the line thickness and returning the foreground color 1142sub _distill_gdSpecial { 1143 my ($self,$type) = @_; 1144 # Save the previous line thickness so I can restore after drawing... 1145 $self->{prev_line_thickness} = $self->_get_thickness() || 1; 1146 my $thickness = $self->{$type}->{thickness}; 1147 $thickness ||= 1; 1148 my $color; 1149 if ($type eq 'gdStyled') { 1150 # Calculate the size in pixels of each dash 1151 # The first color only will be used starting with the first 1152 # dash; remaining dashes will become gaps 1153 my @colors = @{$self->{$type}->{color}}; 1154 my ($prev,@dashes,$dash_length); 1155 foreach (@colors) { 1156 if (!$prev) { 1157 $dash_length = 1; 1158 # Numeric comparisons work for normal colors 1159 # but fail for named special colors like gdTransparent 1160 } elsif ($prev && $prev eq $_) { 1161 $dash_length++; 1162 } elsif ($prev && $prev ne $_) { 1163# } elsif ($prev && $prev == $_) { 1164# $dash_length++; 1165# } elsif ($prev && $prev != $_) { 1166 push (@{$self->{dasharray}},$dash_length); 1167 $dash_length = 1; 1168 } 1169 $prev = $_; 1170 } 1171 push (@{$self->{dasharray}},$dash_length); 1172 $color = $colors[0]; 1173 } else { 1174 $color = $self->{$type}->{color}; 1175 } 1176 1177 $self->setThickness($thickness); 1178 return $color; 1179} 1180 1181 1182# Reset presistent drawing settings between uses of stylized brushes 1183sub _reset { 1184 my $self = shift; 1185 $self->{line_thickness} = $self->{prev_line_thickness} || $self->{line_thickness}; 1186 $self->{prev_line_thickness} = undef; 1187 delete $self->{dasharray}; 1188} 1189 1190# SVG needs some self-awareness so that post-drawing operations can 1191# occur. This is accomplished by tracking all of the pixels that have 1192# been filled in thus far. 1193sub _save { 1194 my ($self) = @_; 1195 # my $path = $img->get_path(x=>[$x1,$x2],y=>[$y1,$y2],-type=>'polyline',-closed=>1); 1196 # foreach (keys %$path) { 1197 # print STDERR $_,"\t",$path->{$_},"\n"; 1198 # } 1199 # push (@{$self->{pixels_filled}},$path); 1200} 1201 1202# Value-access methods 1203# Get the thickness of the line (if it has been set) 1204sub _get_thickness { return shift->{line_thickness} } 1205 1206# return the internal GD object 1207sub _gd { return shift->{gd} } 1208 1209################################################## 1210# GD::SVG::Polygon 1211################################################## 1212package GD::SVG::Polygon; 1213use GD::Polygon; 1214use vars qw(@ISA); 1215@ISA = 'GD::Polygon'; 1216 1217sub _error { 1218 my ($self,$method) = @_; 1219 GD::SVG::Image->_error($method); 1220} 1221 1222sub DESTROY { } 1223 1224# Generic Font package for accessing height and width information 1225# and for formatting strings 1226package GD::SVG::Font; 1227 1228use vars qw/@ISA/; 1229@ISA = qw(GD::SVG); 1230 1231# Return guestimated values on the font height and width 1232sub width { return shift->{width}; } 1233sub height { return shift->{height}; } 1234sub font { return shift->{font}; } 1235sub weight { return shift->{weight}; } 1236sub nchars { shift->_error('nchars')} # NOT SUPPORTED!! 1237 1238# Build the formatting hash for each font... 1239sub formatting { 1240 my $self = shift; 1241 my $size = $self->height; 1242 my $font = $self->font; 1243 my $weight = $self->weight; 1244 my %format = ('font-size' => $size, 1245 'font' => $font, 1246# 'writing-mode' => 'tb', 1247 ); 1248 $format{'font-weight'} = $weight if ($weight); 1249 return \%format; 1250} 1251 1252sub Tiny { return GD::SVG::gdTinyFont; } 1253sub Small { return GD::SVG::gdSmallFont; } 1254sub MediumBold { return GD::SVG::gdMediumBoldFont; } 1255sub Large { return GD::SVG::gdLargeFont; } 1256sub Giant { return GD::SVG::gdGiantFont; } 1257 1258sub _error { 1259 my ($self,$method) = @_; 1260 GD::SVG::Image->_error($method); 1261} 1262 1263sub DESTROY { } 1264 12651; 1266 1267=pod 1268 1269=head1 NAME 1270 1271GD::SVG - Seamlessly enable SVG output from scripts written using GD 1272 1273=head1 SYNOPSIS 1274 1275 # use GD; 1276 use GD::SVG; 1277 1278 # my $img = GD::Image->new(); 1279 my $img = GD::SVG::Image->new(); 1280 1281 # $img->png(); 1282 $img->svg(); 1283 1284=head1 DESCRIPTION 1285 1286GD::SVG painlessly enables scripts that utilize GD to export scalable 1287vector graphics (SVG). It accomplishes this task by wrapping SVG.pm 1288with GD-styled method calls. To enable this functionality, one need 1289only change the "use GD" call to "use GD::SVG" (and initial "new" 1290method calls). 1291 1292=head1 EXPORTS 1293 1294GD::SVG exports the same methods as GD itself, overriding those 1295methods. 1296 1297=head1 USAGE 1298 1299In order to generate SVG output from your script using GD::SVG, you 1300will need to first 1301 1302 # use GD; 1303 use GD::SVG; 1304 1305After that, each call to the package classes that GD implements should 1306be changed to GD::SVG. Thus: 1307 1308 GD::Image becomes GD::SVG::Image 1309 GD::Font becomes GD::SVG::Font 1310 1311=head1 DYNAMICALLY SELECTING SVG OUTPUT 1312 1313If you would like your script to be able to dynamically select either 1314PNG or JPEG output (via GD) or SVG output (via GD::SVG), you should 1315place your "use" statement within an eval. In the example below, each 1316of the available classes is created at the top of the script for 1317convenience, as well as the image output type. 1318 1319 my $package = shift; 1320 eval "use $package"; 1321 my $image_pkg = $package . '::Image'; 1322 my $font_pkg = $package . '::Font'; 1323 1324 # Creating new images thus becomes 1325 my $image = $image_pkg->new($width,$height); 1326 1327 # Establish the image output type 1328 my $image_type; 1329 if ($package = 'GD::SVG') { 1330 $image_type = 'svg'; 1331 } else { 1332 $image_type = 'png'; 1333 } 1334 1335Finally, you should change all GD::Image and GD::Font references to 1336$image_pkg-> and $font_pkg->, respectively. 1337 1338 GD::Image->new() becomes $image_pkg->new() 1339 GD::Font->Large() becomes $font_pkg->Large() 1340 1341The GD::Polygon and GD::Polyline classes work with GD::SVG without 1342modification. 1343 1344If you make heavy use of GD's exported methods, it may also be 1345necessary to add () to the endo of method names to avoide bareword 1346compilation errors. That's the price you pay for using exported 1347functions! 1348 1349=head1 IMPORTANT NOTES 1350 1351GD::SVG does not directly generate SVG, but instead relies upon 1352SVG.pm. It is not intended to supplant SVG.pm. Furthermore, since 1353GD::SVG is, in essence an API to an API, it may not be suitable for 1354applications where speed is of the essence. In these cases, GD::SVG 1355may provide a short-term solution while scripts are re-written to 1356enable more direct output of SVG. 1357 1358Many of the GD::SVG methods accept additional parameters (which are in 1359turn reflected in the SVG.pm API) that are not supported in GD. Look 1360through the remainder of this document for options on specific In 1361addition, several functions have yet to be mapped to SVG.pm 1362calls. Please see the section below regarding regarding GD functions 1363that are missing or altered in GD::SVG. 1364 1365A similar module (SVG::GD) implements a similar wrapper around 1366GD. Please see the section at the bottom of this document that 1367compares GD::SVG to SVG::GD. 1368 1369=head1 PREREQUISITES 1370 1371GD::SVG requires the Ronan Oger's SVG.pm module, Lincoln Stein's GD.pm 1372module, libgd and its dependencies. 1373 1374=head1 GENERAL DIFFICULTIES IN TRANSLATING GD TO SVG 1375 1376These are the primary weaknesses of GD::SVG. 1377 1378=over 4 1379 1380=item SVG requires unique identifiers for each element 1381 1382Each element in an SVG image requires a unique identifier. In general, 1383GD::SVG handles this by automatically generating unique random 1384numbers. In addition to the typical parameters for GD methods, 1385GD::SVG methods allow a user to pass an optional id parameter for 1386naming the object. 1387 1388=item Direct calls to the GD package will fail 1389 1390You must change direct calls to the classes that GD invokes: 1391 GD::Image->new() should be changed to GD::SVG::Image->new() 1392 1393See the documentation above for how to dynamically switch between 1394packages. 1395 1396=item raster fill() and fillToBorder() not supported 1397 1398As SVG documents are not inherently aware of their canvas, the flood 1399fill methods are not currently supported. 1400 1401=item getPixel() not supported. 1402 1403Although setPixel() works as expected, its counterpart getPixel() is 1404not supported. I plan to support this method in a future release. 1405 1406=item No support for generation of images from filehandles or raw data 1407 1408GD::SVG works only with scripts that generate images directly in the 1409code using the GD->new(height,width) approach. newFrom() methods are 1410not currently supported. 1411 1412=item Tiled fills are not supported 1413 1414Any functions passed gdTiled objects will die. 1415 1416=item Styled and Brushed lines only partially implemented 1417 1418Calls to the gdStyled and gdBrushed functions via a 1419rather humorous kludge (and simplification). Depending on the 1420complexity of the brush, they may behave from slightly differently to 1421radically differently from their behavior under GD. You have been 1422warned. See the documentation sections for the methods that set these 1423options (setStyle(), setBrush(), and setTransparent()). 1424 1425=back 1426 1427See below for a full list of methods that have not yet been 1428implemented. 1429 1430=head1 WHEN THINGS GO WRONG 1431 1432GD is a complicated module. Translating GD methods into those 1433required to draw in SVG are not always direct. You may or may not get 1434the output you expect. In general, some tweaking of image parameters 1435(like text height and width) may be necessary. 1436 1437If your script doesn't work as expected, first check the list of 1438methods that GD::SVG provides. Due to differences in the nature of 1439SVG images, not all GD methods have been implemented in GD::SVG. 1440 1441If your image doesn't look as expected, try tweaking specific aspects 1442of image generation. In particular, check for instances where you 1443calculate dimensions of items on the fly like font->height. In SVG, 1444the values of fonts are defined explicitly. 1445 1446=head1 GD FUNCTIONS MISSING FROM GD::SVG 1447 1448The following GD functions have not yet been incorporated into 1449GD::SVG. If you attempt to use one of these functions (and you have 1450enabled debug warnings via the new() method), GD::SVG will print a 1451warning to STDERR. 1452 1453 Creating image objects: 1454 GD::Image->newPalette([$width,$height]) 1455 GD::Image->newTrueColor([$width,$height]) 1456 GD::Image->newFromPng($file, [$truecolor]) 1457 GD::Image->newFromPngData($data, [$truecolor]) 1458 GD::Image->newFromJpeg($file, [$truecolor]) 1459 GD::Image->newFromJpegData($data, [$truecolor]) 1460 GD::Image->newFromXbm($file) 1461 GD::Image->newFromWMP($file) 1462 GD::Image->newFromGd($file) 1463 GD::Image->newFromGdData($data) 1464 GD::Image->newFromGd2($file) 1465 GD::Image->newFromGd2Data($data) 1466 GD::Image->newFromGd2Part($file,srcX,srcY,width,height) 1467 GD::Image->newFromXpm($filename) 1468 1469 Image methods: 1470 $gddata = $image->gd 1471 $gd2data = $image->gd2 1472 $wbmpdata = $image->wbmp([$foreground]) 1473 1474 Color control methods: 1475 $image->colorAllocateAlpha() 1476 $image->colorClosest() 1477 $image->colorClosestHWB() 1478 $image->getPixel() 1479 $image->transparent() 1480 1481 Special Colors: 1482 $image->setBrush() (semi-supported, with kludge) 1483 $image->setStyle() (semi-supported, with kludge) 1484 gdTiled 1485 $image->setAntialiased() 1486 gdAntiAliased() 1487 $image->setAntiAliasedDontBlend() 1488 1489 Drawing methods: 1490 $image->dashedLine() 1491 $image->fill() 1492 $image->fillToBorder() 1493 1494 Image copying methods 1495 None of the image copying methods are yet supported 1496 1497 Image transformation methods 1498 None of the image transformation methods are yet supported 1499 1500 Character and string drawing methods 1501 $image->stringUp() - incompletely supported - broken 1502 $image->charUp() 1503 $image->stringFT() 1504 1505 Alpha Channels 1506 $image->alphaBlending() 1507 $image->saveAlpha() 1508 1509 Miscellaneous image methods 1510 $image->isTrueColor() 1511 $image->compare($image2) 1512 $image->clip() 1513 $image->boundsSafe() 1514 1515 GD::Polyline 1516 Supported without modifications 1517 1518 Font methods: 1519 $font->nchars() 1520 $font->offset() 1521 1522=head1 GROUPING FUNCTIONS GD::SVG 1523 1524GD::SVG supports three additional methods that provides the ability to 1525recursively group objects: 1526 1527=over 4 1528 1529=item $this->startGroup([$id,\%style]), $this->endGroup() 1530 1531These methods start and end a group in a procedural manner. Once a 1532group is started, all further drawing will be appended to the group 1533until endGroup() is invoked. You may optionally pass a string ID and 1534an SVG styles hash to startGroup. 1535 1536=item $group = $this->newGroup([$id,\%style]) 1537 1538This method returns a GD::Group object, which has all the behaviors of 1539a GD::SVG object except that it draws within the current group. You 1540can invoke this object's drawing methods to draw into a group. The 1541group is closed once the object goes out of scope. While the object is 1542open, invoking drawing methods on the parent GD::SVG object will also 1543draw into the group until it goes out of scope. 1544 1545Here is an example of using grouping in the procedural way: 1546 1547 use GD::SVG; 1548 my $img = GD::SVG::Image->new(500,500); 1549 my $white = $img->colorAllocate(255,255,255); 1550 my $black = $img->colorAllocate(0,0,0); 1551 my $blue = $img->colorAllocate(0,0,255); 1552 my $red = $img->colorAllocate(255,0,0); 1553 1554 $img->startGroup('circle in square'); 1555 $img->rectangle(100,100,400,400,$blue); 1556 1557 $img->startGroup('circle and boundary'); 1558 $img->filledEllipse(250,250,200,200,$red); 1559 $img->ellipse(250,250,200,200,$black); 1560 1561 $img->endGroup; 1562 $img->endGroup; 1563 1564 print $img->svg; 1565 1566Here is an example of using grouping with the GD::Group object: 1567 1568 ... 1569 1570 my $g1 = $img->newGroup('circle in square'); 1571 $g1->rectangle(100,100,400,400,$blue); 1572 1573 my $g2 = $g1->startGroup('circle and boundary'); 1574 $g2->filledEllipse(250,250,200,200,$red); 1575 $g2->ellipse(250,250,200,200,$black); 1576 1577 print $img->svg; 1578 1579Finally, here is a fully worked example of using the GD::Simple module 1580to make the syntax cleaner: 1581 1582 #!/usr/bin/perl 1583 1584 use strict; 1585 use GD::Simple; 1586 1587 GD::Simple->class('GD::SVG'); 1588 1589 my $img = GD::Simple->new(500,500); 1590 $img->bgcolor('white'); 1591 $img->fgcolor('blue'); 1592 1593 my $g1 = $img->newGroup('circle in square'); 1594 $g1->rectangle(100,100,400,400); 1595 $g1->moveTo(250,250); 1596 1597 my $g2 = $g1->newGroup('circle and boundary'); 1598 $g2->fgcolor('black'); 1599 $g2->bgcolor('red'); 1600 $g2->ellipse(200,200); 1601 1602 print $img->svg; 1603 1604=back 1605 1606=head1 GD VERSUS GD::SVG METHODS 1607 1608All GD::SVG methods mimic the naming and interface of GD methods. As 1609such, maintenance of GD::SVG follows the development of both GD and 1610SVG. Much of the original GD documentation is replicated here for ease 1611of use. Subtle differences in the implementation of these methods 1612between GD and GD::SVG are discussed below. In particular, the return 1613value for some GD::SVG methods differs from its GD counterpart. 1614 1615=head1 OBJECT CONSTRUCTORS: CREATING IMAGES 1616 1617GD::SVG currently only supports the creation of image objects via its 1618new constructor. This is in contrast to GD proper which supports the 1619creation of images from previous images, filehandles, filenames, and 1620data. 1621 1622=over 4 1623 1624=item $image = GD::SVG::Image->new($height,$width,$debug); 1625 1626Create a blank GD::SVG image object of the specified dimensions in 1627pixels. In turn, this method will create a new SVG object and store it 1628internally. You can turn on debugging with the GD::SVG specific $debug 1629parameter. This should be boolean true and will cause non-implemented 1630methods to print a warning on their status to STDERR. 1631 1632=back 1633 1634=head1 GD::SVG::Image METHODS 1635 1636Once a GD::Image object is created, you can draw with it, copy it, and 1637merge two images. When you are finished manipulating the object, you 1638can convert it into a standard image file format to output or save to 1639a file. 1640 1641=head2 Image Data Output Methods 1642 1643GD::SVG implements a single output method, svg()! 1644 1645=over 4 1646 1647=item $svg = $image->svg(); 1648 1649This returns the image in SVG format. You may then print it, pipe it 1650to an image viewer, or write it to a file handle. For example, 1651 1652 $svg_data = $image->svg(); 1653 open (DISPLAY,"| display -") || die; 1654 binmode DISPLAY; 1655 print DISPLAY $svg_data; 1656 close DISPLAY; 1657 1658if you'd like to return an inline version of the image (instead of a 1659full document version complete with the DTD), pass the svg() method the 1660'inline' flag: 1661 1662 $svg_data = $image->svg(-inline=>'true'); 1663 1664Calling the other standard GD image output methods (eg 1665jpeg,gd,gd2,png) on a GD::SVG::Image object will cause your script to 1666exit with a warning. 1667 1668=back 1669 1670=head2 Color Control 1671 1672These methods allow you to control and manipulate the color table of a 1673GD::SVG image. In contrast to GD which uses color indices, GD::SVG 1674passes stringified RGB triplets as colors. GD::SVG, however, maintains 1675an internal hash structure of colors and colored indices in order to 1676map GD functions that manipulate the color table. This typically 1677requires behind-the-scenes translation of these stringified RGB 1678triplets into a color index. 1679 1680=over 4 1681 1682=item $stringified_color = $image->colorAllocate(RED,GREEN,BLUE) 1683 1684Unlike GD, colors need not be allocated in advance in SVG. Unlike GD 1685which returns a color index, colorAllocate returns a formatted string 1686compatible with SVG. Simultaneously, it creates and stores internally 1687a GD compatible color index for use with GD's color manipulation 1688methods. 1689 1690 returns: "rgb(RED,GREEN,BLUE)" 1691 1692=item $index = $image->colorAllocateAlpha() 1693 1694NOT IMPLEMENTED 1695 1696=item $image->colorDeallocate($index) 1697 1698Provided with a color index, remove it from the color table. 1699 1700=item $index = $image->colorClosest(red,green,blue) 1701 1702This returns the index of the color closest in the color table to the 1703red green and blue components specified. This method is inherited 1704directly from GD. 1705 1706 Example: $apricot = $myImage->colorClosest(255,200,180); 1707 1708NOT IMPLEMENTED 1709 1710=item $index = $image->colorClosestHWB(red,green,blue) 1711 1712NOT IMPLEMENTED 1713 1714=item $index = $image->colorExact(red,green,blue) 1715 1716Retrieve the color index of an rgb triplet (or -1 if it has yet to be 1717allocated). 1718 1719NOT IMPLEMENTED 1720 1721=item $index = $image->colorResolve(red,green,blue) 1722 1723NOT IMPLEMENTED 1724 1725=item $colors_total = $image->colorsTotal() 1726 1727Retrieve the total number of colors indexed in the image. 1728 1729=item $index = $image->getPixel(x,y) 1730 1731NOT IMPLEMENTED 1732 1733=item ($red,$green,$blue) = $image->rgb($index) 1734 1735Provided with a color index, return the RGB triplet. In GD::SVG, 1736color indexes are replaced with actual RGB triplets in the form 1737"rgb($r,$g,$b)". 1738 1739=item $image->transparent($colorIndex); 1740 1741Control the transparency of individual colors. 1742 1743NOT IMPLEMENTED 1744 1745=back 1746 1747=head2 Special Colors 1748 1749GD implements a number of special colors that can be used to achieve 1750special effects. They are constants defined in the GD:: namespace, 1751but automatically exported into your namespace when the GD module is 1752loaded. GD::SVG offers limited support for these methods. 1753 1754=over 4 1755 1756=item $image->setBrush($brush) (KLUDGE ALERT) 1757 1758=item gdBrushed 1759 1760In GD, one can draw lines and shapes using a brush pattern. Brushes 1761are just images that you can create and manipulate in the usual way. 1762When you draw with them, their contents are used for the color and 1763shape of the lines. 1764 1765To make a brushed line, you must create or load the brush first, then 1766assign it to the image using setBrush(). You can then draw in that 1767with that brush using the gdBrushed special color. It's often useful 1768to set the background of the brush to transparent so that the 1769non-colored parts don't overwrite other parts of your image. 1770 1771 # Via GD, this is how one would set a Brush 1772 $diagonal_brush = new GD::Image(5,5); 1773 $white = $diagonal_brush->colorAllocate(255,255,255); 1774 $black = $diagonal_brush->colorAllocate(0,0,0); 1775 $diagonal_brush->transparent($white); 1776 $diagonal_brush->line(0,4,4,0,$black); # NE diagonal 1777 1778GD::SVG offers limited support for setBrush (and the corresponding 1779gdBrushed methods) - currently only in the shapes of squares. 1780Internally, GD::SVG extracts the longest dimension of the image using 1781the getBounds() method. Next, it extracts the second color set, 1782assuming that to be the foreground color. It then re-calls the 1783original drawing method with these new values in place of the 1784gdBrushed. See the private _distill_gdSpecial method for the internal 1785details of this operation. 1786 1787=item $image->setThickness($thickness) 1788 1789Lines drawn with line(), rectangle(), arc(), and so forth are 1 pixel 1790thick by default. Call setThickness() to change the line drawing 1791width. 1792 1793=item $image->setStyle(@colors) 1794 1795setStyle() and gdStyled() are partially supported in GD::SVG. GD::SVG 1796determines the alternating pattern of dashes, treating the first 1797unique color encountered in the array as on, the second as off and so 1798on. The first color in the array is then used to draw the actual line. 1799 1800=item gdTiled 1801 1802NOT IMPLEMENTED 1803 1804=item gdStyled() 1805 1806The GD special color gdStyled is partially implemented in 1807GD::SVG. Only the first color will be used to generate the dashed 1808pattern specified in setStyle(). See setStyle() for additional 1809information. 1810 1811=item $image->setAntiAliased($color) 1812 1813NOT IMPLEMENTED 1814 1815=item gdAntiAliased 1816 1817NOT IMPLEMENTED 1818 1819=item $image->setAntiAliasedDontBlend($color,[$flag]) 1820 1821NOT IMPLEMENTED 1822 1823=back 1824 1825=head2 Drawing Commands 1826 1827=over 4 1828 1829=item $image->setPixel($x,$y,$color) 1830 1831Set the corresponding pixel to the given color. GD::SVG implements 1832this by drawing a single dot in the specified color at that position. 1833 1834=item $image->line(x1,y1,x2,y2,color); 1835 1836Draw a line between the two coordinate points with the specified 1837color. Passing an optional id will set the id of that SVG 1838element. GD::SVG also supports drawing with the special brushes - 1839gdStyled and gdBrushed - although these special styles are difficult 1840to replicate precisley in GD::SVG. 1841 1842=item $image->dashedLine($x1,$y1,$x2,$y2,$color); 1843 1844NOT IMPLEMENTED 1845 1846=item $image->rectangle($x1,$y1,$x2,$y2,$color); 1847 1848This draws a rectangle with the specified color. (x1,y1) and (x2,y2) 1849are the upper left and lower right corners respectively. You may also 1850draw with the special colors gdBrushed and gdStyled. 1851 1852=item $image->filledRectangle($x1,$y1,$x2,$y2,$color); 1853 1854filledRectangle is a GD specific method with no direct equivalent in 1855SVG. GD::SVG translates this method into an SVG appropriate method by 1856passing the filled color parameter as a named 'filled' parameter to 1857SVG. Drawing with the special colors is also permitted. See the 1858documentation for the line() method for additional details. 1859 1860 GD call: 1861 $img->filledRectangle($x1,$y1,$x2,$y2,$color); 1862 1863 SVG call: 1864 $img->rectangle(x=> $x1,y=> $y1, 1865 width => $x2-$x1, 1866 height => $y2-$y1, 1867 fill => $color 1868 1869=item $image->polygon($polygon,$color); 1870 1871This draws a polygon with the specified color. The polygon must be 1872created first (see "Polygons" below). The polygon must have at least 1873three vertices. If the last vertex doesn't close the polygon, the 1874method will close it for you. Both real color indexes and the special 1875colors gdBrushed, gdStyled and gdStyledBrushed can be specified. See 1876the documentation for the line() method for additional details. 1877 1878 $poly = new GD::Polygon; 1879 $poly->addPt(50,0); 1880 $poly->addPt(99,99); 1881 $poly->addPt(0,99); 1882 $image->polygon($poly,$blue); 1883 1884=item $image->filledPolygon($polygon,$color); 1885 1886This draws a polygon filled with the specified color. Drawing with 1887the special colors is also permitted. See the documentation for the 1888line() method for additional details. 1889 1890 # make a polygon 1891 $poly = new GD::Polygon; 1892 $poly->addPt(50,0); 1893 $poly->addPt(99,99); 1894 $poly->addPt(0,99); 1895 1896 # draw the polygon, filling it with a color 1897 $image->filledPolygon($poly,$peachpuff); 1898 1899=item $image->filledPolygon($polygon,$color); 1900 1901This draws a polygon filled with the specified color. Drawing with 1902the special colors is also permitted. See the documentation for the 1903line() method for additional details. 1904 1905 # make a polygon 1906 $poly = new GD::Polygon; 1907 $poly->addPt(50,0); 1908 $poly->addPt(99,99); 1909 $poly->addPt(0,99); 1910 1911 # draw the polygon, filling it with a color 1912 $image->filledPolygon($poly,$peachpuff); 1913 1914=item $image->polyline(polyline,color) 1915 1916 $image->polyline($polyline,$black) 1917 1918This draws a polyline with the specified color. 1919Both real color indexes and the special 1920colors gdBrushed, gdStyled and gdStyledBrushed can be specified. 1921 1922Neither the polyline() method or the polygon() method are very picky: 1923you can call either method with either a GD::Polygon or a 1924GD::Polyline. The I<method> determines if the shape is "closed" or 1925"open" as drawn, I<not> the object type. 1926 1927=item $image-E<gt>polydraw(polything,color) 1928 1929 $image->polydraw($poly,$black) 1930 1931This method draws the polything as expected (polygons are closed, 1932polylines are open) by simply checking the object type and calling 1933either $image->polygon() or $image->polyline(). 1934 1935=item $image->ellipse($cx,$cy,$width,$height,$color) 1936 1937=item $image->filledEllipse($cx,$cy,$width,$height,$color) 1938 1939These methods() draw ellipses. ($cx,$cy) is the center of the arc, and 1940($width,$height) specify the ellipse width and height, respectively. 1941filledEllipse() is like ellipse() except that the former produces 1942filled versions of the ellipse. Drawing with the special colors is 1943also permitted. See the documentation for the line() method for 1944additional details. 1945 1946=item $image->arc($cy,$cy,$width,$height,$start,$end,$color); 1947 1948This draws arcs and ellipses. (cx,cy) are the center of the arc, and 1949(width,height) specify the width and height, respectively. The 1950portion of the ellipse covered by the arc are controlled by start and 1951end, both of which are given in degrees from 0 to 360. Zero is at the 1952top of the ellipse, and angles increase clockwise. To specify a 1953complete ellipse, use 0 and 360 as the starting and ending angles. To 1954draw a circle, use the same value for width and height. 1955 1956Internally, arc() calls the ellipse() method of SVG.pm. Drawing with 1957the special colors is also permitted. See the documentation for the 1958line() method for additional details. 1959 1960Currently, true arcs are NOT supported, only those where the start and 1961end equal 0 and 360 respectively resulting in a closed arc. 1962 1963=item $image->filledArc($cx,$cy,$width,$height,$start,$end,$color 1964[,$arc_style]) 1965 1966This method is like arc() except that it colors in the pie wedge with 1967the selected color. $arc_style is optional. If present it is a 1968bitwise OR of the following constants: 1969 1970gdArc connect start & end points of arc with a rounded edge 1971gdChord connect start & end points of arc with a straight line 1972gdPie synonym for gdChord 1973gdNoFill outline the arc or chord 1974gdEdged connect beginning and ending of the arc to the center 1975 1976gdArc and gdChord are mutally exclusive. gdChord just connects the 1977starting and ending angles with a straight line, while gdArc pro- 1978duces a rounded edge. gdPie is a synonym for gdArc. gdNoFill indi- 1979cates that the arc or chord should be outlined, not filled. gdEdged, 1980used together with gdNoFill, indicates that the beginning and ending 1981angles should be connected to the center; this is a good way to 1982outline (rather than fill) a "pie slice." 1983 1984Using these special styles, you can easily draw bordered ellipses and 1985circles. 1986 1987# Create the filled shape: 1988$image->filledArc($x,$y,$width,$height,0,360,$fill); 1989# Now border it. 1990$image->filledArc($x,$y,$width,$height,0,360,$color,gdNoFill); 1991 1992=item $image->fill(); 1993 1994NOT IMPLEMENTED 1995 1996=item $image->fillToBorder() 1997 1998NOT IMPLEMENTED 1999 2000=back 2001 2002=head2 Image Copying Methods 2003 2004The basic copy() command is implemented in GD::SVG. You can copy one 2005GD::SVG into another GD::SVG, or copy a GD::Image or GD::Simple object 2006into a GD::SVG, thereby embedding a pixmap image into the SVG image. 2007 2008All other image copying methods are unsupported, and if your script 2009calls one of the following methods, your script will die remorsefully 2010with a warning. With sufficient demand, I might try to implement some 2011of these methods. For now, I think that they are beyond the intent of 2012GD::SVG. 2013 2014 $image->clone() 2015 $image->copyMerge() 2016 $image->copyMergeGray() 2017 $image->copyResized() 2018 $image->copyResampled() 2019 $image->trueColorToPalette() 2020 2021=head2 Image Transfomation Commands 2022 2023None of the image transformation commands are implemented in GD::SVG. 2024If your script calls one of the following methods, your script will 2025die remorsefully with a warning. With sufficient demand, I might try 2026to implement some of these methods. For now, I think that they are 2027beyond the intent of GD::SVG. 2028 2029 $image = $sourceImage->copyRotate90() 2030 $image = $sourceImage->copyRotate180() 2031 $image = $sourceImage->copyRotate270() 2032 $image = $sourceImage->copyFlipHorizontal() 2033 $image = $sourceImage->copyFlipVertical() 2034 $image = $sourceImage->copyTranspose() 2035 $image = $sourceImage->copyReverseTranspose() 2036 $image->rotate180() 2037 $image->flipHorizontal() 2038 $image->flipVertical() 2039 2040=head2 Character And String Drawing 2041 2042GD allows you to draw characters and strings, either in normal 2043horizon- tal orientation or rotated 90 degrees. In GD, these routines 2044use a GD::Font object. Internally, GD::SVG mimics the behavior of GD 2045with respect to fonts in a very similar manner, using instead a 2046GD::SVG::Font object described in more detail below. 2047 2048GD's font handling abilities are not as flexible as SVG and it does 2049not allow the dynamic creation of fonts, instead exporting five 2050available fonts as global variables: gdGiantFont, gdLargeFont, 2051gdMediumBoldFont, gdSmallFont and gdTinyFont. GD::SVG also exports 2052these same global variables but establishes them in a different manner 2053using constant variables to establish the font family, font height and 2054width of these global fonts. These values were chosen to match as 2055closely as possible GD's output. If unsatisfactory, adjust the 2056constants at the top of this file. In all subroutines below, GD::SVG 2057passes a generic GD::SVG::Font object in place of the exported font 2058variables. 2059 2060=over 4 2061 2062=item $image->string($font,$x,$y,$string,$color) 2063 2064This method draws a string starting at position (x,y) in the speci- 2065fied font and color. Your choices of fonts are gdSmallFont, 2066gdMediumBoldFont, gdTinyFont, gdLargeFont and gdGiantFont. 2067 2068 $myImage->string(gdSmallFont,2,10,"Peachy Keen",$peach); 2069 2070=item $image->stringUp($font,$x,$y,$string,$color) 2071 2072Same as the previous example, except that it draws the text rotated 2073counter-clockwise 90 degrees. 2074 2075=item $image->char($font,$x,$y,$char,$color) 2076 2077=item $image->charUp($font,$x,$y,$char,$color) 2078 2079These methods draw single characters at position (x,y) in the spec- 2080ified font and color. They're carry-overs from the C interface, where 2081there is a distinction between characters and strings. Perl is 2082insensible to such subtle distinctions. Neither is SVG, which simply 2083calls the string() method internally. 2084 2085=item @bounds = $image->stringFT($fgcolor,$font- 2086 name,$ptsize,$angle,$x,$y,$string) 2087 2088=item @bounds = $image->stringFT($fgcolor,$font- 2089 name,$ptsize,$angle,$x,$y,$string,\%options) 2090 2091In GD, these methods use TrueType to draw a scaled, antialiased 2092strings using the TrueType font of your choice. GD::SVG can handle 2093this directly generating by calling the string() method internally. 2094 2095 The arguments are as follows: 2096 2097 fgcolor Color index to draw the string in 2098 fontname An absolute path to the TrueType (.ttf) font file 2099 ptsize The desired point size (may be fractional) 2100 angle The rotation angle, in radians 2101 x,y X and Y coordinates to start drawing the string 2102 string The string itself 2103 2104GD::SVG attempts to extract the name of the font from the pathname 2105supplied in the fontname argument. If it fails, Helvetica will be used 2106instead. 2107 2108If successful, the method returns an eight-element list giving the 2109boundaries of the rendered string: 2110 2111 @bounds[0,1] Lower left corner (x,y) 2112 @bounds[2,3] Lower right corner (x,y) 2113 @bounds[4,5] Upper right corner (x,y) 2114 @bounds[6,7] Upper left corner (x,y) 2115 2116This from the GD documentation (not yet implemented in GD::SVG): 2117 2118An optional 8th argument allows you to pass a hashref of options to 2119stringFT(). Two hashkeys are recognized: linespacing, if present, 2120controls the spacing between lines of text. charmap, if present, sets 2121the character map to use. 2122 2123The value of linespacing is supposed to be a multiple of the char- 2124acter height, so setting linespacing to 2.0 will result in double- 2125spaced lines of text. However the current version of libgd (2.0.12) 2126does not do this. Instead the linespacing seems to be double what is 2127provided in this argument. So use a spacing of 0.5 to get separation 2128of exactly one line of text. In practice, a spacing of 0.6 seems to 2129give nice results. Another thing to watch out for is that successive 2130lines of text should be separated by the "\r\n" characters, not just 2131"\n". 2132 2133The value of charmap is one of "Unicode", "Shift_JIS" and "Big5". The 2134interaction between Perl, Unicode and libgd is not clear to me, and 2135you should experiment a bit if you want to use this feature. 2136 2137 $gd->stringFT($black,'/dosc/windows/Fonts/pala.ttf',40,0,20,90, 2138 "hi there\r\nbye now", 2139 {linespacing=>0.6, 2140 charmap => 'Unicode', 2141 }); 2142 2143For backward compatibility with older versions of the FreeType 2144library, the alias stringTTF() is also recognized. Also be aware that 2145relative font paths are not recognized due to problems in the libgd 2146library. 2147 2148=item $hasfontconfig = $image-E<gt>useFontConfig($flag) 2149 2150Call useFontConfig() with a value of 1 in order to enable support for 2151fontconfig font patterns (see stringFT). Regardless of the value of 2152$flag, this method will return a true value if the fontconfig library 2153is present, or false otherwise. 2154 2155NOT IMPLEMENTED 2156 2157=back 2158 2159=head2 Alpha Channels 2160 2161=over 4 2162 2163=item $image->alphaBlending($blending) 2164 2165NOT IMPLEMENTED 2166 2167=item $image->saveAlpha($saveAlpha) 2168 2169NOT IMPLEMENTED 2170 2171=back 2172 2173=head2 Miscellaneous Image Methods 2174 2175=over 4 2176 2177=item $image->interlaced([$flag]) 2178 2179NOT IMPLEMENTED 2180 2181=item ($width,$height) = $image->getBounds() 2182 2183getBounds() returns the height and width of the image. 2184 2185=item $is_truecolor = $image->isTrueColor() 2186 2187NOT IMPLEMENTED 2188 2189=item $flag = $image1->compare($image2) 2190 2191NOT IMPLEMENTED 2192 2193=item $image->clip($x1,$y1,$x2,$y2) 2194 ($x1,$y1,$x2,$y2) = $image->clip 2195 2196NOT IMPLEMENTED 2197 2198=item $flag = $image->boundsSafe($x,$y) 2199 2200NOT IMPLEMENTED 2201 2202=back 2203 2204=head1 GD::SVG::Polygon METHODS 2205 2206SVG is much more adept at creating polygons than GD. That said, GD 2207does provide some rudimentary support for polygons but must be created 2208as seperate objects point by point. 2209 2210=over 4 2211 2212=item $poly = GD::SVG::Polygon->new 2213 2214Create an empty polygon with no vertices. 2215 2216 $poly = new GD::SVG::Polygon; 2217 2218=item $poly->addPt($x,$y) 2219 2220Add point (x,y) to the polygon. 2221 2222 $poly->addPt(0,0); 2223 $poly->addPt(0,50); 2224 $poly->addPt(25,25); 2225 2226=item ($x,$y) = $poly->getPt($index) 2227 2228Retrieve the point at the specified vertex. 2229 2230 ($x,$y) = $poly->getPt(2); 2231 2232=item $poly->setPt($index,$x,$y) 2233 2234Change the value of an already existing vertex. It is an error to set 2235a vertex that isn't already defined. 2236 2237 $poly->setPt(2,100,100); 2238 2239=item ($x,$y) = $poly->deletePt($index) 2240 2241Delete the specified vertex, returning its value. 2242 2243 ($x,$y) = $poly->deletePt(1); 2244 2245=item $poly->toPt($dx,$dy) 2246 2247Draw from current vertex to a new vertex, using relative (dx,dy) 2248coordinates. If this is the first point, act like addPt(). 2249 2250 $poly->addPt(0,0); 2251 $poly->toPt(0,50); 2252 $poly->toPt(25,-25); 2253 2254NOT IMPLEMENTED 2255 2256=item $vertex_count = $poly->length() 2257 2258Return the number of vertices in the polygon. 2259 2260=item @vertices = $poly->vertices() 2261 2262Return a list of all the verticies in the polygon object. Each mem- 2263ber of the list is a reference to an (x,y) array. 2264 2265 @vertices = $poly->vertices; 2266 foreach $v (@vertices) 2267 print join(",",@$v),"\n"; 2268 } 2269 2270=item @rect = $poly->bounds() 2271 2272Return the smallest rectangle that completely encloses the polygon. 2273The return value is an array containing the (left,top,right,bottom) of 2274the rectangle. 2275 2276 ($left,$top,$right,$bottom) = $poly->bounds; 2277 2278=item $poly->offset($dx,$dy) 2279 2280Offset all the vertices of the polygon by the specified horizontal 2281(dh) and vertical (dy) amounts. Positive numbers move the polygon 2282down and to the right. Returns the number of vertices affected. 2283 2284 $poly->offset(10,30); 2285 2286=item $poly->map($srcL,$srcT,$srcR,$srcB,$destL,$dstT,$dstR,$dstB) 2287 2288Map the polygon from a source rectangle to an equivalent position in a 2289destination rectangle, moving it and resizing it as necessary. See 2290polys.pl for an example of how this works. Both the source and 2291destination rectangles are given in (left,top,right,bottom) coordi- 2292nates. For convenience, you can use the polygon's own bounding box as 2293the source rectangle. 2294 2295 # Make the polygon really tall 2296 $poly->map($poly->bounds,0,0,50,200); 2297 2298NOT IMPLEMENTED 2299 2300=item $poly->scale($sx,$sy) 2301 2302Scale each vertex of the polygon by the X and Y factors indicated by 2303sx and sy. For example scale(2,2) will make the polygon twice as 2304large. For best results, move the center of the polygon to position 2305(0,0) before you scale, then move it back to its previous position. 2306 2307NOT IMPLEMENTED 2308 2309=item $poly->transform($sx,$rx,$sy,$ry,$tx,$ty) 2310 2311Run each vertex of the polygon through a transformation matrix, where 2312sx and sy are the X and Y scaling factors, rx and ry are the X and Y 2313rotation factors, and tx and ty are X and Y offsets. See the Adobe 2314PostScript Reference, page 154 for a full explanation, or experiment. 2315 2316NOT IMPLEMENTED 2317 2318=back 2319 2320=head2 GD::Polyline 2321 2322Please see GD::Polyline for information on creating open polygons and 2323splines. 2324 2325=head1 GD::SVG::Font METHODS 2326 2327NOTE: The object-oriented implementation to font utilites is not yet 2328supported. 2329 2330The libgd library (used by the Perl GD library) has built-in support 2331for about half a dozen fonts, which were converted from public-domain 2332X Windows fonts. For more fonts, compile libgd with TrueType support 2333and use the stringFT() call. 2334 2335GD::SVG replicates the internal fonts of GD by hardcoding fonts which 2336resemble the design and point size of the original. Each of these 2337fonts is available both as an imported global (e.g. gdSmallFont) and 2338as a package method (e.g. GD::Font->Small). 2339 2340=over 4 2341 2342=item gdTinyFont 2343 2344=item GD::Font->Tiny 2345 2346This is a tiny, almost unreadable font, 5x8 pixels wide. 2347 2348=item gdSmallFont 2349 2350=item GD::Font->Small 2351 2352This is the basic small font, "borrowed" from a well known public 2353domain 6x12 font. 2354 2355=item gdMediumBoldFont 2356 2357=item GD::Font->MediumBold 2358 2359This is a bold font intermediate in size between the small and large 2360fonts, borrowed from a public domain 7x13 font; 2361 2362=item gdLargeFont 2363 2364=item GD::Font->Large 2365 2366This is the basic large font, "borrowed" from a well known public 2367domain 8x16 font. 2368 2369=item gdGiantFont 2370 2371=item GD::Font->Giant 2372 2373This is a 9x15 bold font converted by Jan Pazdziora from a sans serif 2374X11 font. 2375 2376=item $font->nchars 2377 2378This returns the number of characters in the font. 2379 2380 print "The large font contains ",gdLargeFont->nchars," characters\n"; 2381 2382NOT IMPLEMENTED 2383 2384=item $font->offset() 2385 2386This returns the ASCII value of the first character in the font 2387 2388=item $width = $font->width 2389 2390=item $height = $font->height 2391 2392These return the width and height of the font. 2393 2394 ($w,$h) = (gdLargeFont->width,gdLargeFont->height); 2395 2396=back 2397 2398=head1 REAL WORLD EXAMPLES 2399 2400=over 4 2401 2402=item BioPerl 2403 2404The Bio::Graphics package of the BioPerl project makes use of GD::SVG 2405to export SVG graphics. 2406 2407 http://www.bioperl.org/ 2408 2409=item Generic Genome Browser 2410 2411The Generic Genome Browser (GBrowse) utilizes Bio::Graphics and 2412enables SVG dumping of genomics views. You can see a real-world 2413example of SVG output from GBrowse at WormBase: 2414 2415 http://www.wormbase.org/cgi-bin/gbrowse/ 2416 2417Further information about the Generic Genome Browser is available at 2418the Generic Model Organism Project home page: 2419 2420 http://www.gmod.org/ 2421 2422=item toddot 2423 2424I've also prepared a number of comparative images at my website 2425(shameless plug, hehe): 2426 2427 http://www.toddot.net/projects/GD-SVG/ 2428 2429=back 2430 2431=head1 INTERNAL METHODS 2432 2433The following internal methods are private and documented only for 2434those wishing to extend the GD::SVG interface. 2435 2436=over 4 2437 2438=item _distill_gdSpecial() 2439 2440When a drawing method is passed a stylized brush via gdBrushed, the 2441internal _distill_gdSpecial() method attempts to make sense of this by 2442setting line thickness and foreground color. Since stylized brushes 2443are GD::SVG::Image objects, it does this by fetching the width of the 2444image using the getBounds method. This width is then used to 2445setThickness. The last color set by colorAllocate is then used for 2446the foreground color. 2447 2448In setting line thickness, GD::SVG temporarily overrides any 2449previously set line thickness. In GD, setThickness is persistent 2450through uses of stylized brushes. To accomodate this behavior, 2451_distill_gdSpecial() temporarily stores the previous line_thickness in 2452the $self->{previous_line_thickness} flag. 2453 2454=item _reset() 2455 2456The _reset() method is used to restore persistent drawing settings 2457between uses of stylized brushes. Currently, this involves 2458 2459 - restoring line thickness 2460 2461=back 2462 2463=head1 IMPORTANT NOTE! GD::SVG / SVG::GD 2464 2465A second module (SVG::GD), written by Ronan Oger also provides similar 2466functionality as this module. Ronan and I are concurrently developing 2467these modules with an eye towards integrating them in the future. In 2468principle, the primary difference is that GD::SVG aims to generate SVG 2469and SVG only. That is, it: 2470 2471 1. Does not store an internal representation of the GD image 2472 2473 2. Does not enable JPG, PNG, OR SVG output from a single pass 2474 through data 2475 2476 3. Only occasioanally uses inherited methods from GD 2477 2478Instead GD::SVG depends on the user to choose which output format they 2479would like in advance, "use"ing the appropriate module for that 2480output. As described at the start of this document, module selection 2481between GD and GD::SVG can be made dynamically using eval statements 2482and variables for the differnet classes that GD and GD::SVG create. 2483 2484There is a second reason for not maintaining a double representation 2485of the data in GD and SVG format: SVG documents can quickly become 2486very large, especially with large datasets. In cases where scripts are 2487primarily generating png images in a server environment and would only 2488occasionally need to export SVG, gernerating an SVG image in parallel 2489would result in an unacceptable performance hit. 2490 2491Thus GD::SVG aims to be a plugin for existing configurations that 2492depend on GD but would like to take advantage of SVG output. 2493 2494SVG::GD, on the other hand, aims to tie in the raster-editing ability 2495of GD with the power of SVG output. In part, it aims to do this by 2496inheriting many methods from GD directly and bringing them into the 2497functional space of GD. This makes SVG::GD easier to set up initially 2498(simply by adding the "use SVG::GD" below the "use GD" statement of 2499your script. GD::SVG sacrfices this initial ease-of-setup for more 2500targeted applications. 2501 2502=head1 ACKNOWLEDGEMENTS 2503 2504Lincoln Stein, my postdoctoral mentor, author of GD.pm, and all around 2505Perl stud. Ronan Oger, author of SVG.pm conceptualized and implemented 2506another wrapper around GD at about the exact same time as this module. 2507He also provided helpful discussions on implementing GD functions into 2508SVG. Oliver Drechsel and Marc Lohse provided patches to actually 2509make the stringUP method functional. 2510 2511=head1 AUTHOR 2512 2513Todd Harris, PhD E<lt>harris@cshl.orgE<gt> 2514 2515=head1 COPYRIGHT AND LICENSE 2516 2517Copyright @ 2003-2005 Todd Harris and the Cold Spring Harbor Laboratory 2518 2519This library is free software; you can redistribute it and/or modify 2520it under the same terms as Perl itself. 2521 2522=head1 SEE ALSO 2523 2524L<GD>, 2525L<SVG>, 2526L<SVG::Manual>, 2527L<SVG::DOM> 2528 2529=cut 2530