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