UML::Sequence::Svg - converts xml sequence files to svg


UML-Sequence documentation Contained in the UML-Sequence distribution.

Index


Code Index:

NAME

Top

UML::Sequence::Svg - converts xml sequence files to svg

SYNOPSIS

Top

    use UML::Sequence::Svg;

    seq2svg @ARGV;

DESCRIPTION

Top

This module supports the seq2svg.pl script like Pod::Html supports pod2html. The array passed to seq2svg.pl should have the following form:

 ([ I<options-pairs>, ] [input_file_name])

where options-pairs are any of

-a color

specifies a color to be used to fill the activation boxes. Must be in a form acceptable to SVG.

-c color

specifies a color to be used to fill the class boxes. Must be in a form acceptable to SVG.

-e

specifies that embedded annotations are to be applied to any rasterized version of the SVG image. Only valid when the -m or -M option is also specified. When an arrow label has associated annotations, the labels with be suffixed with a superscript number linking to a text section containing the annotation text.

-g arrow-gap

If arrow-gap is an integer value, specifies number of pixels between arrows (default is 40 pixels). If arrow-gap is a fractional value, specifies a scaling factor for the default number of pixels between arrows.

-j

specifies that Javascript'ed tooltip annotations are to be applied to any rasterized version of the SVG image. Only valid when the -m or -M option is also specified. When an arrow label has associated annotations, the hyperlinks in the areamap for the label will include onmouseover() function calls containing the annotation text for use with the Javascript tooltip package available at http://www.walterzorn.com/tooltip/tooltip_e.htm.

-m areamap-path, -M areamap-path

specifies the name of a file to receive HTML containing an image element, areamap, and (optionally) either an ordered list of annotations (if -e was specified) or a script tag linking to the Javascript tooltip script (if -j was specified). to be applied to any rasterized version of the SVG image. Only valid when either the -p or -P option is specified. -M specifies append mode for the output file.

-o output_file_name

specifies the output file name.

-p classdocs-path, -P classdocs-path

specifies a base path to classdocs generated by psichedoc. -p causes hyperlinks to the documents for individual classes and/or methods to be embedded in the SVG file for both the class labels and method labels, excluding method labels w/ embedded whitespace. Additionally, the specified path is used with any generated HTML imagemap. -P is the same, except it does not embed hyperlinks in the SVG (due to Batik's current inability to handle rasterization of SVG's with embeded hyperlinks). In other words, use -P to specify a path for HTML imagemaps when the SVG output will be further processed by Batik.

-w box-width

specifies width of class box in pixels; default is 125. Used to compute class header boxes and areamap coordinates.

-x char-width

specifies width of characters in pixels; default is 6. Used to compute class header boxes and areamap coordinates.

-y char-height

specifies height of characters in pixels; default is 14. Used to compute class header boxes and areamap coordinates.

By default input is from standard in and output is to standard out.

SEE ALSO

Top

UML::Sequence::Raster

AUTHOR

Top

Phil Crow, <philcrow2000@yahoo.com>

Version 0.02 Updates by Dean Arnold, <darnold@presicient.com>

COPYRIGHT

Top


UML-Sequence documentation Contained in the UML-Sequence distribution.
package UML::Sequence::Svg;

use Exporter;
@ISA    = qw(Exporter);
@EXPORT = qw(seq2svg);

use XML::DOM;
use Getopt::Std;

use strict;
use warnings;

our $VERSION = '0.02';

# Constant declarations.
my $CLASS_TEXT_Y      =  40;
my $CLASS_BOX_Y       =  25;
my $CLASS_BOX_HEIGHT  =  20;
my $CLASS_BOX_WIDTH   = 125;
my $CLASS_SPACING     =   3;

my $LEFT_EDGE         =  30;

my $ACTIVATION_WIDTH  =  15;
my $ACTIVATION_OFFSET =  10;

my $FIRST_ARROW       =  55;
my $ARROW_SPACING     =  40;

# Global variable:
my $output_file = "-";
my $classcolor = 'white';
my $actcolor = 'white';
my $docpath;            # path to assoc. classdocs
my $hyperlink = 0;      # true => embed hyperlinks
my $mappath;            # path to write areamap file
my $mapname;            # imagemap name derived from SVG output filename
my $charwidth = 10;     # width of characters
my $charht = 10;        # height of characters
my $annot;              # annotations behavior:
                        # 'e' : embed in specified file;
                        # 'j' : apply javascript tooltips in specified file
                        # NOTE: specified file may be same as image map file

sub seq2svg {
    local (@ARGV) = @_;
    my $opts = parse_command_line();

    $classcolor = $opts->{c} || 'white';
    $actcolor = $opts->{a} || 'white';
    $docpath = $opts->{p} || $opts->{P};
    $hyperlink = defined($opts->{p});
    $mappath = $opts->{m} || $opts->{M};
    $charwidth = $opts->{x} || 6;
    $charht = $opts->{y} || 14;
    $CLASS_BOX_WIDTH = $opts->{w} || 125;
    $annot = $opts->{e} ? 'e' : $opts->{j} ? 'j' : undef;

    die "Annotation requested without output path."
        if ($annot && (! $mappath));

    $ARROW_SPACING = (index($opts->{g}, '.') >= 0) ?
        int($ARROW_SPACING * $opts->{g}) : $opts->{g}
        if $opts->{g};

    $docpath .= '/'
        if ($docpath && (substr($docpath, -1, 1) ne '/'));

    my $input_file = shift @ARGV;

    if (defined $input_file) {
        open INPUT, "$input_file"
            or die "Couldn't open $input_file for input: $!\n";
    }
    else {
        *INPUT = *STDIN;
    }
#
#    DAA Add HTML image map rendering
#
    my $mapfd;
    if (defined($mappath)) {
        $mappath = $opts->{M} ? ">>$mappath" : ">$mappath";
        die "Cannot open image map file $mappath: $!"
            unless open($mapfd, $mappath);
#
#    define a mapname from output file name (if any)
#
        $mapname = ($output_file=~/(\w+)\.\w+$/) ? $1 : 'mapname';
        print $mapfd
"<html>
<body>
<img src='$mapname\.png' usemap='#$mapname'>
<MAP NAME='$mapname'>
";
    }

    my $parser       = XML::DOM::Parser->new();
    my $doc          = $parser->parse(*INPUT);

    my $sequence     = $doc->getDocumentElement();
    my $title        = $sequence->getAttribute("title");

    my $classes      = $doc->getElementsByTagName("class");
#
#    DAA added to track previous class for drawing async arrows
#
    my %priors       = ();
    my $class_output = draw_classes($classes, $mapfd, \%priors);
    my $class_hash   = build_class_name_hash($classes);
    my $arrow_output = draw_arrows($doc, $class_hash, $mapfd, \%priors);
    my $class_count  = scalar (keys %$class_hash);
    my $arrow_count  = count_arrows($doc);
    my $width        =
        ($class_count  + 1) * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + 40;
    my $height       = 2.5 * $CLASS_TEXT_Y + $arrow_count * ($ARROW_SPACING);

    open SVGOUT, ">$output_file";

    print SVGOUT <<EOJ;
<?xml version="1.0"?>
  <svg xmlns="http://www.w3.org/2000/svg" height="$height" width="$width">
    <defs>
      <style type="text/css">
              rect, line, path { stroke-width: 2; stroke: black }
              text { font-weight: bold }
      <marker orient="auto" refY="2.5" refX="4" markerHeight="7" markerWidth="6" id="mArrow">
        <path style="fill: black; stroke: none" d="M 0 0 6 3 0 7"/>
      </marker>
      <marker orient="auto" refY="2.5" refX="4" markerHeight="7" markerWidth="6" id="mRtHalfArrow">
        <path style="fill: black; stroke: none" d="M 0 7 6 2 0 2"/>
      </marker>

      <marker orient="0 deg" refY="2.5" refX="4" markerHeight="7" markerWidth="6" id="mLtHalfArrow">
        <path style="fill: black; stroke: none" d="M 0 2 6 2 6 7"/>
      </marker>
      </style>
    </defs>
EOJ

    if ($title) {
        print SVGOUT <<EOJ;
    <text x="5" y="15">
        $title
    </text>
EOJ
    }

    print SVGOUT <<EOJ;
$class_output$arrow_output</svg>
EOJ
#
#    DAA terminate areamap
#
    if (defined($mapfd)) {
        print $mapfd '
<script language="JavaScript" type="text/javascript" src="../wz_tooltip.js"></script>

</body>
</html>
'
            if $opts->{j};
        close $mapfd;
    }

}

sub draw_classes {
  my $classes = shift;
  my $mapfd = shift;
  my $priors = shift;
  my $retval;

  my $x        = $LEFT_EDGE;
  my $box_left = $LEFT_EDGE - 8;
  my $y        = $CLASS_TEXT_Y;
  my $max_extent;
  my $boxht;
  my $boxtext;

  my $prior = '_EXTERNAL';
  for (my $i = 0; $i < $classes->getLength(); $i++) {
    my $class      = $classes->item($i);
    my $life_x     = int($x + $CLASS_BOX_WIDTH / 2);
    my $class_name = $class  ->getAttribute("name");

    if ($class_name eq '_EXTERNAL') {
        $retval = '';
    }
    else {

    $priors->{$class_name} = $prior;
    $prior = $class_name;

    ($boxht, $boxtext) = _wrapText($class_name, $x);
#
#    DAA add hyperlink to psichedocs
#
    my $class_path = ($docpath && ($class_name!~/\s/)) ?
        $docpath . join('/', split(/::/, $class_name)) . '.html' : undef;

    my $born       = $class  ->getAttribute("born")
                     * $ARROW_SPACING + $FIRST_ARROW;
    my $extends_to = ($class ->getAttribute("extends-to") + 1)
                     * $ARROW_SPACING + $FIRST_ARROW;

    if (not defined $max_extent) { $max_extent = $extends_to; }
#
#    DAA rearranged to place text on top of rectangle
#    for fill purposes
#
    $retval .= "  <rect style='fill: $classcolor' height='$boxht' "
            . "width='$CLASS_BOX_WIDTH' y='$CLASS_BOX_Y' x='$box_left' />\n";

    $retval .= "<a xlink:href='$class_path'>"
        if $hyperlink;

    $retval .= "<text y='$CLASS_TEXT_Y' x='$x'>$boxtext</text>\n";

    $retval .= "</a>"
        if $hyperlink;
#
#    DAA support areamaps
#
    print $mapfd "<AREA TITLE='$class_name'",
        ($class_path ? " HREF='$class_path'" : ''),
        " SHAPE=RECT COORDS='$box_left,$CLASS_BOX_Y,",
        $box_left + $CLASS_BOX_WIDTH, ',', $CLASS_BOX_Y + $boxht, "'>\n"
        if $mapfd;

    $retval .= "  <line style='stroke-dasharray: 4,4; ' fill='none' "
            .  "stroke='black' x1='$life_x' y1='$born' x2='$life_x' "
            .  "y2='$max_extent' />\n";

    my $activation_x = int($box_left + $CLASS_BOX_WIDTH / 2);
    my @activations  = $class->getElementsByTagName("activation");
    foreach my $activation (@activations) {
      my $born       = $activation->getAttribute("born");
      my $extends_to = $activation->getAttribute("extends-to");
      my $offset     = $activation->getAttribute("offset");
      my $top        = $FIRST_ARROW  + $born       * $ARROW_SPACING;
      my $height     = ($extends_to  - $born + .5) * $ARROW_SPACING;
      my $left       = $activation_x + $offset     * $ACTIVATION_OFFSET;
      $retval .= "    <rect style='fill: $actcolor' height='$height' "
          . "width='$ACTIVATION_WIDTH' y='$top' x='$left'/>\n";
    }
    }
    $x        += $CLASS_BOX_WIDTH + $CLASS_SPACING;
    $box_left += $CLASS_BOX_WIDTH + $CLASS_SPACING;
    $retval .= "\n";
  }
  return $retval;
}

sub count_arrows {
  my $doc    = shift;
  my $arrows = $doc->getElementsByTagName("arrow");
  return $arrows->getLength();
}

sub draw_arrows {
  my $doc        = shift;
  my $class_hash = shift;
  my $mapfd      = shift;
  my $priors = shift;
  my $retval;

  my $arrows     = $doc->getElementsByTagName("arrow");
  my $annotnum = 1;
  my $annotspan = "\n<ol>\n" if ($annot && ($annot eq 'e'));

  for (my $i = 0; $i < $arrows->getLength(); $i++) {
    my $arrow = $arrows->item($i);
    my $from         = $arrow->getAttribute("from"       );
    my $to           = $arrow->getAttribute("to"         );
#
#    DAA 12/24/2005
#    use type attribute to specify returnvalue or external
#    which changes the line style to dashed or folded, respectively
#    also note that $from for external events originate at the far left
#
    my $type         = $arrow->getAttribute("type"       );
    my $label        = $arrow->getAttribute("label"      );
    my $from_offset  = $arrow->getAttribute("from-offset");
    my $to_offset    = $arrow->getAttribute("to-offset"  );
    my $annots       = $arrow->getElementsByTagName('annotation');
    $annots          = $annots->item(0) if $annots;
    my $y            = $FIRST_ARROW + ($i + 1) * $ARROW_SPACING;

    my $from_number  = ($from eq '_EXTERNAL') ?
        $class_hash->{$priors->{$to}} : $class_hash->{$from};
    my $to_number    = $class_hash->{$to};
    $label =~ s/</&lt;/g;
    $label =~ s/>/&gt;/g;
    my $class_path;
    my $labmethod = $hyperlink ? \&drawHyperLabel : \&drawLabel;
#
#    DAA add hyperlink to psichedocs
#
    if ($docpath && ($to!~/\s/)) {
        my $doclabel = $label;
        $doclabel=~s/[\*!]//g;
        $doclabel=~s/^\s*\[[^\]]*\]\s*//;
        $doclabel=~s/^\s+//;
        $doclabel=~s/\s+$//;
        $class_path = $docpath . join('/', split(/::/, $to)) .
            ".html#$doclabel"
            unless ($doclabel=~/\s/);
    }

    if    ($from_number < $to_number) {  # arrow from left to right
      my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2
               + $from_offset * $ACTIVATION_OFFSET;
      $x1 += 20 if ($from eq '_EXTERNAL');
      my $x2 = $to_number   * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH - $ACTIVATION_WIDTH)/2;
#
#    DAA cuddle the label to the arrowhead
#
      my $xlab = $x2 - $CLASS_SPACING - 6 -
          ($label ? ($charwidth * length($label)) : $charwidth);
      my $ylab = $y  - 6;
      my $xend = $label ? $xlab + ($charwidth * length($label)) : undef;
      my $yend = $ylab + $charht;
#
#    DAA changed to support call vs. return vs. async activations
#
      $retval .= "<line x1='$x1' y1='$y' x2='$x2' y2='$y' style='" .
              (($type eq 'return') ? "stroke-dasharray: 4,4; " : ' ') .
              'marker-end: url(#' .
              (($type eq 'async') ? "mRtHalfArrow);' />\n" : "mArrow);' />\n");
#
#    DAA need to add hrefs for methods, but we'll need to track the "to" class
#    name, and ignore names w/ whitespace
#
        $retval .= $labmethod->($mapfd, $xlab, $ylab, $xend, $yend,
            $type, $label, $class_path, $mapname, $i,
            \$annotnum, $annots, \$annotspan)
            if defined($label);
    }
    elsif ($from_number > $to_number) {  # arrow from right to left
      my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH - $ACTIVATION_WIDTH)/2;
      my $x2 = $to_number   * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2
               + $to_offset * $ACTIVATION_OFFSET;
#
#    DAA changed to support call vs. return vs. async activations
#    (note async activations always go from left to right, so we won't get
#    any here...)
#
      $retval .= "<line x1='$x1' y1='$y' x2='$x2' y2='$y' style='" .
              (($type eq 'return') ? "stroke-dasharray: 4,4; " : ' ') .
              "marker-end: url(#mArrow);' />\n";

      my $xlab = $x2 + $CLASS_SPACING + 6;
      my $ylab = $y  - 6;
      my $xend = $label ? $xlab + ($charwidth * length($label)) : undef;
      my $yend = $ylab + $charht;
#
#    DAA need to add hrefs for methods, but we'll need to track the "to" class
#    name, and ignore names w/ whitespace
#
        $retval .= $labmethod->($mapfd, $xlab, $ylab, $xend, $yend,
            $type, $label, $class_path, $mapname, $i,
            \$annotnum, $annots, \$annotspan)
            if defined($label);
    }
    else {               # arrow from and to same class
      my $x1 = $from_number * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2
               + $from_offset * $ACTIVATION_OFFSET;
      my $x2 = $to_number   * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + $LEFT_EDGE
               + ($CLASS_BOX_WIDTH + $ACTIVATION_WIDTH)/2
               + $to_offset * $ACTIVATION_OFFSET;
      $y -= 10;
      my $y2 = $y + 20;
      my $x1padded = $x1 + $ACTIVATION_OFFSET + 15;
      $retval .= "<line x1='$x1' y1='$y' x2='$x1padded' y2='$y' />\n"
              .  "<line x1='$x1padded' y1='$y' x2='$x1padded' y2='$y2' />\n"
              .  "<line x1='$x1padded' y1='$y2' x2='$x2' y2='$y2' "
              .  "style='marker-end: url(#mArrow);' />\n";
      my $xlab = $x1padded + $CLASS_SPACING;
      my $ylab = ($y + $y2) / 2;
      my $xend = $label ? $xlab + ($charwidth * length($label)) : undef;
      my $yend = $ylab + $charht;
#
#    DAA need to add hrefs for methods, but we'll need to track the "to" class
#    name, and ignore names w/ whitespace
#
        $retval .= $labmethod->($mapfd, $xlab, $ylab, $xend, $yend,
            $type, $label, $class_path, $mapname, $i,
            \$annotnum, $annots, \$annotspan)
            if defined($label);

    }
  }
#
#    DAA save annotations if any
#
    if ($mapfd) {
        print $mapfd "</MAP>\n\n";
        print $mapfd "$annotspan\n</ol>\n"
              if $annotspan && ($annotspan ne '<ol>');
    }

  return $retval;
}

sub build_class_name_hash {
  my $class_nodes = shift;
  my %classes;  # keyed by class name store left to right position

  for (my $i = 0; $i < $class_nodes->getLength(); $i++) {
    my $class = $class_nodes->item($i);
    my $class_name = $class->getAttribute("name");
    $classes{$class_name} = $i;
  }
  return \%classes;
}
#
#    DAA to wrap long class names in the header boxes
#
sub _wrapText {
    my ($text, $xpos) = @_;

    return ($CLASS_BOX_HEIGHT, $text)
        if ((length($text) * $charwidth) < ($CLASS_BOX_WIDTH - 10));
#
#    split on whitespace, dot/colon/underscore, or
#    lowercaseUppercase
#
    my $maxChars = int(($CLASS_BOX_WIDTH - 10)/$charwidth);
    my @lines = ();
    my @pieces = ();
    while ($text=~s/([^:\.\s_]+)((:+)|\.|_|\s+)?//) {
        my ($t, $p) = ($1, $2);
        push @pieces, $t;
#
#    if the text is still too long, look for lowerUpper
#
        if (length($pieces[-1]) <= $maxChars) {
            push(@pieces, $p) if $p;
            next;
        }

        $t = pop @pieces;
        push @pieces, $1
            while ($t=~s/^(.*?[a-z])([A-Z].*)$/$2/);
        push @pieces, $t if ($t ne '');

        push(@pieces, $p) if $p;
    }
#
#    now reassemble to minimize box height
#
    $lines[0] = shift @pieces;
    foreach (@pieces) {
        $lines[-1] .= $_,
        next
            if (length($lines[-1]) <= $maxChars) &&
                ((length($lines[-1]) + length($_)) <= $maxChars);
        push @lines, $_ unless /^\s+$/;
    }

    my $otext = "<tspan x='$xpos'>" .
        join("</tspan>\n<tspan x='$xpos' dy='1em'>", @lines) . '</tspan>';
    return (($charht + 2) * scalar @lines, $otext);

}

sub parse_command_line {
    my %opts;
    getopts('a:c:eg:jm:M:o:p:P:w:x:y:', \%opts);
    $output_file = $opts{o} if defined $opts{o};
    $classcolor = $opts{c} || 'white';
    $actcolor = $opts{a} || 'white';
    $docpath = $opts{p} || $opts{P};
    $annot = $opts{e} || $opts{j};
    return \%opts;
}

sub _createLabelMap {
    my ($x1, $y1, $x2, $y2, $title, $name, $path, $annot_text,
        $annot_name, $annot_span) = @_;
#
#    NOTE: Batik seems to render the text coords about 10px lower than SVG,
#    so we'll cheat here...
#
    $y1 -= 10, $y2 -= 10;

    my $maptext =
"<AREA NAME='$name' TITLE='$title' SHAPE=RECT COORDS='$x1,$y1,$x2,$y2' " .
        ($path ? "HREF='$path' " : '');

    if ($annot_text) {
        if ($annot && ($annot eq 'j')) {
#
#    Scripted annotation
#
            $annot_text =~s/'/\\'/g;    # escape single quotes
            $title =~s/'/\\'/g;         # escape single quotes
#
#    NOTE: this must all be on 1 line!!!
#
            $maptext .="
onmouseover=\"this.T_STATIC=true;this.T_FONTCOLOR='black';this.T_FONTSIZE='12px';this.T_BGCOLOR='#e0e0e0';this.T_OPACITY=90;this.T_SHADOWWIDTH=8;return escape('$annot_text')\" >
";
        }
        else {     # annot eq 'e'
#
#    embedded annotation, add anchor and annotation text
#    NOTE: the offsets used here are heuristic, need a bbox jscript
#
            my ($x3, $y3, $y4) = ($x2+4, $y1 + (($y2 - $y1)>>1), $y1 - 4);

            $maptext .= ">
<AREA SHAPE=RECT COORDS='$x2,$y4,$x3,$y3' HREF='#$annot_name'>
";

            $annot_text=~s/</&lt;/g;
            $annot_text=~s/>/&gt;/g;
            $$annot_span .= "
<a name='$annot_name'></a>
<li>$annot_text
<p>
";
        }
    }
    else {
        $maptext .= ">\n";
    }

    return $maptext;
}

sub drawLabel {
    my ($mapfd, $xlab, $ylab, $xend, $yend, $type, $label, $path,
        $mapname, $i, $annotnum, $annots, $annotspan) = @_;

    my $retval = "<text x='$xlab' y='$ylab' " .
#
#    use italics for asyncs
#
        (($type eq 'async') ?
            "style='font-style: italic;'><tspan>" :
            '><tspan>') . $label;
#
#    if annotated, add superscript if embedded
#
    $retval .= "<tspan baseline-shift=\"super\">$$annotnum</tspan>",
    $$annotnum++
        if ($annot && ($annot eq 'e') && $annots);

    $retval .= "</tspan></text>\n";
#
#    DAA support areamaps
#
    print $mapfd _createLabelMap(
        $xlab, $ylab, $xend, $yend,             # location
        $label, "$mapname\_$i", (($type eq 'call') ? $path : undef),
        ($annots ? $annots->getAttribute('text') : undef),
            "$mapname\_annot_$i", $annotspan)    # annotation
        if $mapfd;

    return $retval;
}

sub drawHyperLabel {
    my ($mapfd, $xlab, $ylab, $xend, $yend, $type, $label, $path,
        $mapname, $i, $annotnum, $annots, $annotspan) = @_;

    my $retval = "<text x='$xlab' y='$ylab' " .
#
#    use italics for asyncs
#
        (($type eq 'async') ?
            "style='font-style: italic;'><tspan>" : '><tspan>') .
            ($path ? "<a xlink:href='$path'>$label</a>" : $label);
#
#    if annotated, add superscript if embedded
#
    $retval .= "<tspan baseline-shift=\"super\">
        <a xlink:href='#$mapname\_annot_$i'>$$annotnum</a>
</tspan>",
    $$annotnum++
        if ($annot && ($annot eq 'e') && $annots);

    $retval .= "</tspan></text>\n";
#
#    DAA support areamaps
#
    print $mapfd _createLabelMap(
        $xlab, $ylab, $xend, $yend,             # location
        $label, "$mapname\_$i", (($type eq 'call') ? $path : undef),
        ($annots ? $annots->getAttribute('text') : undef),
            "$mapname\_annot_$i", $annotspan)    # annotation
        if $mapfd;

    return $retval;
}

1;