#2345678901234567890123456789012345678901234567890123456789012345678901234567890 =head1 NAME UML::Sequence::Raster - converts xml sequence files to a raster image =head1 SYNOPSIS use UML::Sequence::Raster; seq2raster @ARGV; =head1 DESCRIPTION This module supports the seq2rast.pl script like Pod::Html supports pod2html. The array passed to seq2rast.pl should have the following form: ([ I, ] [input_file_name]) where I are any of =over 4 =item C<-a color> specifies a color to be used to fill the activation boxes. Must be a string of either hexadecimal RGB values, I B<'#FF00AC0024B1'>, or a name from the following list of supported colors: white lyellow lpurple lbrown lgray yellow purple dbrown gray dyellow dpurple transparent dgray lgreen lorange black green orange lblue dgreen pink blue lred dpink dblue red marine gold dred cyan =item C<-c color> specifies a color to be used to fill the class boxes. See the C<-a> option for acceptable color specifications. =item C<-e> specifies that embedded annotations are to be applied to the raster image. Only valid when the C<-m> or C<-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. =item C<-f "font">, C<-F fontfile> Specifies a font to be used to render text. C<-f> uses a "named" font, e.g., "Times New Roman", which can be resolved to a TrueType font file. The special fontname B may be used to specify the GD builtin fonts (which are also the default on non-Win32 platforms). Values other than B are currently only supported on Win32 platforms with L installed. C<-F> specifies the actual fontfile name, e.g., 'ARIALBD.TTF'. Note that, on Win32 platforms with L installed, using a named font will cause this module to attempt to locate bold (for class name labels) and italic (for event labels) versions of the font file. The base font will be used if either is not found. =item C<-g arrow-gap> If C is an integer value, specifies number of pixels between arrows (default is 40 pixels). If C is a fractional value, specifies a scaling factor for the default number of pixels between arrows. =item C<-j> specifies that Javascript'ed tooltip annotations are to be applied to the raster image. Only valid when the C<-m> or C<-M> option is also specified. When an arrow label has associated annotations, the hyperlinks in the areamap for the label will include C function calls containing the annotation text for use with the Javascript tooltip package available at L. Note that annotated labels will be underlined in the image. =item C<-m areamap-path>, C<-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 L<-e> was specified) or a script tag linking to the Javascript tooltip script (if L<-j> was specified) to be applied to the raster image. Only valid when either the L<-p> or L<-P> option is specified. C<-M> specifies append mode for the output file. =item C<-o output_file_name> specifies the output file name. The format of the image is determined by the file qualifier as follows: .png, .PNG => PNG format .gif, .GIF => GIF format .jpg, .jpeg, .JPG, .JPEG => JPEG format If not specified, output is sent to STDOUT in PNG format. =item C<-p classdocs-path>, C<-P classdocs-path> specifies a base path to classdocs generated by psichedoc. Adds an areamap to the HTML output file specified by the C<-m> or C<-M> option with hyperlinks to the documents for individual classes and/or methods, excluding method labels w/ embedded whitespace. Additionally, the specified path is used with any generated HTML imagemap. Both C<-p> and C<-P> behave the same; both are supported for compatibility with L. =item C<-s "Signature"> specifies a signature to apply to the lower right corner in small text, e.g., "Copyright(C) 2006, GOWI Corp.". =item C<-w box-width> specifies width of class box in pixels; default is 125. Used to compute class header boxes and areamap coordinates. =item C<-x char-width> specifies width of characters in pixels; default is 6. Used to compute class header boxes and areamap coordinates. =item C<-y char-height> specifies height of characters in pixels; default is 14. Used to compute class header boxes and areamap coordinates. =back By default input is from standard in and output is to standard out. =cut package UML::Sequence::Raster; use Exporter; @ISA = qw(Exporter); @EXPORT = qw(seq2raster); use XML::DOM; use Getopt::Std; use GD; use GD qw(gdSmallFont gdLargeFont gdMediumBoldFont gdTinyFont gdGiantFont); use GD::Text::Wrap; if ($^O eq 'MSWin32') { require Win32::Font::NameToFile; import Win32::Font::NameToFile qw(get_ttf_abs_path get_ttf_bold get_ttf_italic); } use strict; use warnings; our $VERSION = '0.01'; # 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 = 92; my $ACTIVATION_WIDTH = 15; my $ACTIVATION_OFFSET = 10; my $FIRST_ARROW = 55; my $ARROW_SPACING = 60; my $ARROW_DELTA = 0; use constant LINE_THICKNESS => 2; use constant LEFT_ARROW => 0; use constant RIGHT_ARROW => 1; use constant HALF_LEFT_ARROW => 2; use constant HALF_RIGHT_ARROW => 3; my @arrowpts = ( [0, 0, 12, 6, 0, 14], [0, 6, 12, 0, 12, 14], [0, 4, 12, 4, 12, 14], [0, 14, 12, 4, 0, 4], ); my @arrows = (); # Global variable: my $output_file = "-"; my $classcolor = 'white'; my $actcolor = 'white'; my $docpath; # path to assoc. classdocs my $mappath; # path to write areamap file my $mapname; # imagemap name derived from raster output filename my $classcharwidth = 9; # width of class header characters my $charwidth = 8; # 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 my $white; my $black; my %colors = ( white => [255,255,255], lgray => [191,191,191], gray => [127,127,127], dgray => [63,63,63], black => [0,0,0], lblue => [0,0,255], blue => [0,0,191], dblue => [0,0,127], gold => [255,215,0], lyellow => [255,255,125], yellow => [255,255,0], dyellow => [127,127,0], lgreen => [0,255,0], green => [0,191,0], dgreen => [0,127,0], lred => [255,0,0], red => [191,0,0], dred => [127,0,0], lpurple => [255,0,255], purple => [191,0,191], dpurple => [127,0,127], lorange => [255,183,0], orange => [255,127,0], pink => [255,183,193], dpink => [255,105,180], marine => [127,127,255], cyan => [0,255,255], lbrown => [210,180,140], dbrown => [165,42,42], transparent => [1,1,1] ); # # font names # my $fontfile; my $fontname; my $titlefont; my $classfont; my $methodfont; my $eventfont; my $tinyfont; my $sig; # # caches of text elements, to optimize # rendering # my @class_hdrs = (); my @classtext = (); my @labeltext = (); my @italtext = (); my @supertext = (); sub seq2raster { local (@ARGV) = @_; my $opts = parse_command_line(); $classcolor = $opts->{c} || 'white'; $actcolor = $opts->{a} || 'white'; $docpath = $opts->{p} || $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; $fontfile = $opts->{F}; $fontname = $opts->{f}; $sig = $opts->{s}; 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 " "; } 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_hash = build_class_name_hash($classes); my $class_count = scalar (keys %$class_hash); # # if external events, but not first, then trim # $class_count-- if exists($class_hash->{_EXTERNAL}) && $class_hash->{_EXTERNAL}; # # compute class header text now, so we get get true box sizes # my $maxClassHt = 1; while (my ($class, $idx) = each %$class_hash) { if ($class eq '_EXTERNAL') { $ARROW_DELTA = 1 if ($idx == 0); next; } $class_hdrs[$idx] = _wrapText($class); my $lines = ($class_hdrs[$idx]=~tr/\n//) + 1; $maxClassHt = $lines if ($lines > $maxClassHt); } $CLASS_BOX_HEIGHT *= $maxClassHt; $FIRST_ARROW = $CLASS_TEXT_Y + $CLASS_BOX_HEIGHT; my $arrow_count = count_arrows($doc); my $width = ($class_count + 0.5) * ($CLASS_BOX_WIDTH + $CLASS_SPACING) + 40; my $height = $FIRST_ARROW + ($arrow_count + 1) * ($ARROW_SPACING); my $format = ($output_file eq '-') ? 'png' : ($output_file=~/\.(gif|jpg|jpeg|png)$/i) ? lc $1 : 'png'; my $img = GD::Image->new($width, $height); $white = $img->colorAllocate(@{$colors{white}}); $black = $img->colorAllocate(@{$colors{black}}); $classcolor = ($classcolor eq 'black') ? $black : ($classcolor eq 'white') ? $white : (substr($classcolor,0,1) eq '#') ? $img->colorAllocate(_dehex($classcolor)) : $img->colorAllocate(@{$colors{$classcolor}}); $actcolor = ($actcolor eq 'black') ? $black : ($actcolor eq 'white') ? $white : (substr($actcolor,0,1) eq '#') ? $img->colorAllocate(_dehex($actcolor)) : $img->colorAllocate(@{$colors{$actcolor}}); # # create arrowhead markers # my ($arrow, $trans, $arrowblack, $poly); foreach (@arrowpts) { $arrow = GD::Image->new(12,14); $trans = $arrow->colorAllocate(128, 128, 128); $arrowblack = $arrow->colorAllocate(0,0,0); $arrow->transparent($trans); $arrow->filledRectangle(0,0,12,14, $trans); $poly = GD::Polygon->new(); $poly->addPt(shift @$_, shift @$_) while scalar @$_; $arrow->filledPolygon($poly, $arrowblack); push @arrows, $arrow; } # # load fonts # set default to GD fonts # $titlefont = gdGiantFont; $classfont = gdGiantFont; $methodfont = gdSmallFont; $eventfont = gdSmallFont; $tinyfont = gdTinyFont; my ($boldfont, $italfont); if (($^O eq 'MSWin32') && ((!$fontname) || (lc $fontname ne 'gd'))) { if ($fontfile) { $boldfont = $fontfile; $italfont = $fontfile; } else { $fontname = 'Arial' unless $fontname; $fontfile = get_ttf_abs_path($fontname); $fontname = 'Arial', $fontfile = get_ttf_abs_path($fontname) unless $fontfile; $boldfont = get_ttf_bold($fontname) || $fontfile; $italfont = get_ttf_italic($fontname) || $fontfile; } $titlefont = $classfont = $boldfont; $methodfont = $eventfont = $tinyfont = $fontfile; } elsif (($^O ne 'MSWin32') && $fontfile) { # don't really know what to do about non-Win32 # $boldfont = $fontfile; # $italfont = $fontfile; $titlefont = $classfont = $boldfont; $methodfont = $eventfont = $tinyfont = $fontfile; } draw_classes($img, $classes, $mapfd, \%priors); draw_arrows($img, $doc, $class_hash, $mapfd, \%priors); render_text($img, $title); # # DAA terminate areamap # if (defined($mapfd)) { print $mapfd ' ' if $opts->{j}; close $mapfd; } open GDOUT, ">$output_file"; binmode GDOUT; print GDOUT $img->$format(); close GDOUT unless $output_file eq '-'; } sub _dehex { my $color = substr($_[0], 1); my ($len, $off1, $off2) = (length($color) == 6) ? (2, 2, 4) : (2, 4, 8); return ((length($color) == 6) || (length($color) == 12)) ? (hex(substr($color, 0, $len)), hex(substr($color, $len, $len)), hex(substr($color, $len << 1, $len))) : (0,0,0); } sub draw_classes { my ($img, $classes, $mapfd, $priors) = @_; my $x = $LEFT_EDGE; my $box_left = $x - 8; my $max_extent; my $boxht = $CLASS_BOX_HEIGHT; 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"); next if ($class_name eq '_EXTERNAL'); $priors->{$class_name} = $prior; $prior = $class_name; # # 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 # $img->setThickness(LINE_THICKNESS); $img->filledRectangle($box_left, $CLASS_BOX_Y, $box_left + $CLASS_BOX_WIDTH, $CLASS_BOX_Y + $boxht, $classcolor); $img->rectangle($box_left, $CLASS_BOX_Y, $box_left + $CLASS_BOX_WIDTH, $CLASS_BOX_Y + $boxht, $black); # # draw class name # push @classtext, [$class_hdrs[$i], $x, 26, $CLASS_BOX_WIDTH]; # # DAA support areamaps # print $mapfd "\n" if $mapfd; $img->dashedLine($life_x, $born, $life_x, $max_extent, $black); my $activation_x = int($box_left + $CLASS_BOX_WIDTH / 2); my @activations = $class->getElementsByTagName("activation"); foreach my $activation (@activations) { $born = $activation->getAttribute("born"); $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; $img->filledRectangle($left, $top, $left + $ACTIVATION_WIDTH, $top + $height, $actcolor); $img->rectangle($left, $top, $left + $ACTIVATION_WIDTH, $top + $height, $black); } # } $x += $CLASS_BOX_WIDTH + $CLASS_SPACING; $box_left += $CLASS_BOX_WIDTH + $CLASS_SPACING; } return $img; } sub count_arrows { my $doc = shift; my $arrows = $doc->getElementsByTagName("arrow"); return $arrows->getLength(); } sub draw_arrows { my ($img, $doc, $class_hash, $mapfd, $priors) = @_; my $arrows = $doc->getElementsByTagName("arrow"); my $annotnum = 1; my $annotspan = "\n
    \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}; $from_number--, $to_number-- if $ARROW_DELTA; $label =~ s//>/g; my $class_path; # # 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; my $ylab = $y - $charht; # # DAA changed to support call vs. return vs. async activations # $img->setThickness(LINE_THICKNESS); ($type eq 'return') ? $img->dashedLine($x1, $y, $x2, $y, $black) : $img->line($x1, $y, $x2, $y, $black); my $arrowimg = $arrows[($type eq 'async') ? HALF_RIGHT_ARROW : LEFT_ARROW]; $img->copy($arrowimg, $x2-14, $y-6, 0,0, $arrowimg->width, $arrowimg->height); # # DAA need to add hrefs for methods, but we'll need to track the "to" class # name, and ignore names w/ whitespace # drawLabel->($img, $mapfd, $xlab, $ylab, 1, $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...) # $img->setThickness(LINE_THICKNESS); ($type eq 'return') ? $img->dashedLine($x1, $y, $x2, $y, $black) : $img->line($x1, $y, $x2, $y, $black); my $arrowimg = $arrows[RIGHT_ARROW]; $img->copy($arrowimg, $x2, $y-6, 0,0, $arrowimg->width, $arrowimg->height); my $xlab = $x2 + $CLASS_SPACING + 6; my $ylab = $y - $charht; # # DAA need to add hrefs for methods, but we'll need to track the "to" class # name, and ignore names w/ whitespace # drawLabel->($img, $mapfd, $xlab, $ylab, 0, $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; $img->setThickness(LINE_THICKNESS); $img->line($x1, $y, $x1padded, $y, $black); $img->line($x1padded, $y, $x1padded, $y2, $black); $img->line($x1padded, $y2, $x2, $y2, $black); my $arrowimg = $arrows[RIGHT_ARROW]; $img->copy($arrowimg, $x2, $y2-6, 0,0, $arrowimg->width, $arrowimg->height); my $xlab = $x1padded + $CLASS_SPACING; my $ylab = ($y + $y2) / 2; # # DAA need to add hrefs for methods, but we'll need to track the "to" class # name, and ignore names w/ whitespace # drawLabel->($img, $mapfd, $xlab, $ylab, 0, $type, $label, $class_path, $mapname, $i, \$annotnum, $annots, \$annotspan) if defined($label); } } # # DAA save annotations if any # if ($mapfd) { print $mapfd "\n\n"; print $mapfd "$annotspan\n
\n" if $annotspan && ($annotspan ne '
    '); } return $img; } sub build_class_name_hash { my $class_nodes = shift; my %classes; # keyed by class name store left to right position my ($class, $class_name); $class = $class_nodes->item($_), $class_name = $class->getAttribute('name'), $classes{$class_name} = $_ for (0..$class_nodes->getLength() - 1); return \%classes; } sub parse_command_line { my %opts; getopts('a:c:ef:F:g:jm:M:o:p:P:s: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}; $fontfile = $opts{F}; $fontname = $opts{f}; $sig = $opts{s}; 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 raster, # so we'll cheat here...may not be needed for GD # $y1 -= 10, $y2 -= 10; # # also, if label has a newline, hyperlink bottom line only # if ($title=~/\n/) { $y1 += 16; $y2 += 16; $x2 = $x1 + ((length($title) - index($title, "\n") - 1) * $charwidth); } $title=~tr/\n/ /; my $maptext = " "; } 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 .= "> "; $annot_text=~s//>/g; $$annot_span .= "
  1. $annot_text

    "; } } else { $maptext .= ">\n"; } return $maptext; } sub render_text { my $img = shift; my $title = shift; my ($align, $len, $nl, $oldlen, $ullen, $x, $xul, $y, $yul); my $textimg = GD::Text::Wrap->new($img, preserve_nl => 1, line_space => 0, align => 'left', color => $black ); # # render title (if any) # $textimg->set_font($titlefont, 12); $textimg->set(text => $title), $textimg->draw(5, 15) if $title; # # render classnames # $textimg->set_font($classfont, 12); foreach (@classtext) { $textimg->set( text => $_->[0], width => $_->[3]); $textimg->draw($_->[1], $_->[2]); } # # render labels # $img->setThickness(1); $textimg->set_font($methodfont, 10); foreach (@labeltext) { # # we need to set alignment based on the arrow direction # $align = $_->[5] ? 'right' : 'left'; $nl = index($_->[0], "\n"); $x = $xul = $_->[1]; ($y, $yul) = ($_->[2] - 4, $_->[2] + 10); $ullen = $len = $textimg->width($_->[0]); if (($nl > 0) && $_->[4]) { $yul += $charht + 4, my @segs = split(/\n/, $_->[0]); $oldlen = $ullen; $ullen = $textimg->width($segs[1]); $xul += ($oldlen - $ullen) if ($align eq 'right') && ($ullen < $oldlen); } $textimg->set( text => $_->[0], align => $align, width => $len); $textimg->draw($x, $y); $img->line($xul, $yul, $xul + $ullen, $yul, $black) if $_->[4]; } # # render italics # $textimg->set_font($eventfont, 10); foreach (@italtext) { $len = $textimg->width($_->[0]); $textimg->set( text => $_->[0], width => $len); $textimg->draw($_->[1], $_->[2] - 4); $img->line($_->[1], $_->[2] + 10, $_->[1] + $len, $_->[2] + 10, $black) if $_->[4]; } # # render superscripts # $textimg->set_font($tinyfont, 8); $textimg->set( text => $_->[0], align => 'left', width => 10), $textimg->draw($_->[1], $_->[2]) foreach (@supertext); # # render signature (if any) # if ($sig) { $textimg->set_font($tinyfont, 6); $len = $textimg->width($sig); $textimg->set( text => $sig, width => $len, align => 'left'); my ($x, $y) = ($img->width - $len - 20, $img->height - 20); $textimg->draw($x, $y) } return 1; } sub drawLabel { my ($img, $mapfd, $xlab, $ylab, $align, $type, $label, $path, $mapname, $i, $annotnum, $annots, $annotspan) = @_; $xlab -= ($charwidth * length($label)) if $align; my $xend = $xlab + ($charwidth * length($label)); my $yend = $ylab + $charht; my $xmap = $xlab; if ($type eq 'async') { push @italtext, [ $label, $xlab, $ylab, $xend - $xlab - 6, (($annot && ($annot eq 'j') && $annots) ? 1 : undef), $align ]; } else { # # if conditional, add line break # if ($label=~s/^(\[[^\]]+\])\s*(.*)$/$1\n$2/) { # # and adjust offsets # my ($top, $bot) = (length($1), length($2)); $top = $bot if ($top < $bot); $xlab = $xend - ($top * $charwidth), $xmap = $xend - ($bot * $charwidth) if $align; $ylab -= ($charht + 6); } push @labeltext, [ $label, $xlab, $ylab, $xend - $xlab - 6, (($annot && ($annot eq 'j') && $annots) ? 1 : undef), $align ]; } # # if annotated, add superscript if embedded # push @supertext, [ $$annotnum++, $xend, $ylab - 5 ] if ($annot && ($annot eq 'e') && $annots); # # DAA support areamaps # $ylab += $charht, $yend += $charht unless ($yend - $ylab > $charht); print $mapfd _createLabelMap( $xmap, $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 $img; } # # to wrap long class names in the header boxes # sub _wrapText { my ($text, $xpos) = @_; return $text if ((length($text) * $classcharwidth) < ($CLASS_BOX_WIDTH - 6)); # # split on whitespace, dot/colon/underscore, or # lowercaseUppercase # my $maxChars = int(($CLASS_BOX_WIDTH - 10)/$classcharwidth); 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+$/; } return join("\n", @lines); } 1; =head1 Application Notes The diagram layout of output of this module differs slightly from the layout of L output; specifically, this module stacks conditionals atop the activation label in order to avoid collisions with activation bars. =head1 SEE ALSO L =head1 AUTHOR Dean Arnold L =head1 COPYRIGHT Copyright (C) 2006, Dean Arnold, Presicient Corp., USA. All rights reserved. Portions Copyright 2003, Philip Crow, all rights reserved. You may modify and/or redistribute this code in the same manner as Perl itself. =cut