# $Id: AttributedString.pm,v 1.9 2003/04/29 18:18:06 malay Exp $ Perl
# module for Pastel::Text::AttributedString
# Author: Malay < curiouser@ccmb.res.in >
# Copyright (c) 2003 Malay K Basu
# You may distribute this module under the same terms as perl itself
Pastel::Text::AttributedString - For drawing text with attributes.
$as = Pastel::Text::AttributedString->new(-text=>"Some text");
$font = Pastel::Font->new(-family=>"Arial", -style=>"bold", -size=>14);
# create a hash containing font
$as1 = Pastel:: Text::AttributedString->new(-text=>"Some text",
my $g = Pastel::Graphics->new();
$g->draw_string($as, 10, 20);
# no x and y coordinates. Remember, we created the hash containing
# set some attributes to $as.
$as->add_attribute("COLOR", Pastel::Color->red, 0, 3);
$g->draw_string($as, 200, 300);
SVG "tspan" elements are supported by this module. Unlike
AttributedString class in Java, you do not pass
AttributedCharacterIterator to the graphics context. But pass the
AttributedString class itself.
The following text attributes are defined and can be used with this module-
FONT A Pastel::Font object
FAMILY A string describing the family name of the font. If the
FONT attibute is already set then this attribute is ignored.
Should not be a composite of more than one attributes. e.g.
"Arial" is a valid attibute value but "Arial-Bold" is not.
POSTURE REGULAR or OBLIQUE or ITALIC
WEIGHT REGULAR or BOLD. Default REGULAR.
COLOR Pastel::Color object.
ANCHOR START or MIDDLE or END
STROKE_WIDTH Numeric value denoting stroke-width in pixels
STROKE_COLOR Pastel::Color object
ROTATE Numeric value indicating the rotation of the glyph
Note that X and Y coordinates are not valid attributes.
Each AttributedString class contains the C<root> attribute, which is
applied to the whole string. The are three ways to put this root
attributes: (1) pass the attributes as hash in the construtor; (2) use
the C<add_attribute()> method with only the first two arguments; (3)
use C<add_attribute()> method with the first index 0 and the last
index equals to the length of the whole string.
In addition to the C<root> element the AttributedString contains
several C<"runs">. Each run is represented by a single C<<tspan>> element.
=head2 Pastel::Text::AttributedString->new(-text=>$string)
Creates and returns an AttributedString object with no attributes defined.
=head2 Pastel::Text::AttributedString->new(-text=>$string, -attributes=>\%att);
Creates and returns an AttributedString object with attributes defined
for the whole string.%att is a hash containing the attributes in key
package Pastel
::Text
::AttributedString
;
@ISA = qw(Pastel::String Pastel::Mixin::Mixin);
#use Pastel::Mixin::Mixin;
string
=> "", # to hold the string value
runcount
=> 0, # Total number of runs
run_start_index
=> [], # holds the beginning index of each run
run_end_index
=> [], # holds the end index of each run
"FONT", "FAMILY", "POSTURE", "WEIGHT",
"SIZE", "COLOR", "UNDERLINE", "STRIKETHROUGH",
"ANCHOR", "STROKE_WIDTH", "STROKE_COLOR", "X",
"Y", "MULTI_X", "MULTI_Y", "ROTATE"
bless $self, ref($class) || $class;
my ( $self, @args ) = @_;
if ( @args < 1 || @args > 4 ) {
croak
"***Illegal parameters in Pastel::Text::AttributedString!\n";
my ( $string, $attributes ) =
$self->_rearrange( [ "TEXT", "ATTRIBUTES" ], @args );
if ( defined($string) ) {
$self->{string
} = $string;
$self->{string_length
} = length $string;
croak
"Illegal parameter in Pastel::Text::AttributedString!\n";
if ( defined($attributes) ) {
#print STDERR "Map supplied", "\n";
foreach my $key ( keys %{$attributes} ) {
if ( $self->_valid_key($key) ) {
push @attribute, $key, ${$attributes}{$key};
$self->_add_to_root_attribute(@attribute);
my $max_index = $self->{string_length
} - 1;
#my (@attributes) = $self->_get_attribute( $args[0], $args[1] );
unless ( $self->_valid_key( $args[0] ) ) {
Pastel::Text::AttributedString::add_attribute!\n";
if ( @args < 2 || @args > 4 ) {
croak
"Illegal parameter in
Pastel:Text:AttributedString::add_attribute!\n";
$self->_add_to_root_attribute( $args[0], $args[1] );
#print STDERR %{$self->{root_attribute}}, "\n";
elsif ( scalar(@args) == 4 ) {
#print STDERR "$args[2], $args[3]","\n";
if ( $args[2] > $args[3] ) {
Pastel::Text::AttributedString::add_attribute\nBegin Index
should be lesser than End Index!\n";
|| $args[3] > $max_index )
"Out of range index in Pastel::Text::AttributedSring::add_attribut!\n";
# check whether the attributes cover the whole string
elsif ( $args[2] == 0 && $args[3] == $max_index ) {
#my (@attributes) = $self->_get_attribute($args[0], $args[1]);
$self->_add_to_root_attribute( $args[0], $args[1] );
elsif ( $self->{runcount
} == 0 ) {
#print STDERR "****Runcount 0\n";
$self->_create_new_run(@args);
$self->_break_run(@args);
my $s = "<text x=\"" . $self->get_x() . '" y="' . $self->get_y() . '"';
# we need to split the string to get splices
my (@string) = split ( //, $self->{string
} );
#my (@root_attr) = $self->_get_root_attributes();
#print STDERR "to_svg: @root_attr\n";
# _get_root_attributes() will return undef if there is no root attribute
if ( $self->_get_root_attributes() ) {
#if ( $self->_get_root_attributes()) {
#print STDERR "***to_svg: root attribute present\n";
my (@root_attr) = $self->_get_root_attributes();
$s .= $self->_get_attributes_as_style(@root_attr);
# do we have any attribute set at all?
if ( $self->{runcount
} > 0 ) {
# loop through each run and add it as <tspan>
for ( my $i = 0 ; $i < $self->{runcount
} ; $i++ ) {
if ( $self->_get_run_attributes_as_array($i) ) {
my (@run_attributes) = $self->_get_run_attributes_as_array($i);
$s .= $self->_get_attributes_as_style(@run_attributes);
#print STDERR "***", ${ $self->{run_start_index} }[$i], ${ $self->{run_end_index}}[$i], "\n";
#print STDERR "***", @string[1..3], "\n";
my $text = join "", @string[ ${ $self->{run_start_index
} }[$i]
.. ${ $self->{run_end_index
} }[$i] ];
# if ( ${ $self->{attributes} }[$i] ) {
# convert the xml entities
if ( ${ $self->{attributes
} }[$i] ) {
else { $s .= $self->{string
}; }
#print STDERR "to_svg: $s\n";
=head2 _create_new_run($key, $value, $start, $end)
If there is no run present this method actually creates a new run. Should be called when $self->{runcount} = 0.
Arguments: Same as add_attribute()
my ( $self, @args ) = @_;
my $max_index = $self->{string_length
} - 1;
my (%attrib) = ( $args[0], $args[1] );
if ( ( $args[2] > 0 ) && $args[3] < $max_index ) {
#print STDERR "****Inside range\n";
#print STDERR "*** Attributes@attributes\n";
push @
{ $self->{run_start_index
} }, 0, $args[2], $args[3] + 1;
push @
{ $self->{run_end_index
} }, $args[2] - 1, $args[3], $max_index;
push @
{ $self->{attributes
} }, "", \
%attrib, "";
elsif ( $args[2] == 0 && $args[3] < $max_index ) {
push @
{ $self->{run_start_index
} }, $args[2], $args[3] + 1;
push @
{ $self->{run_end_index
} }, $args[3], $max_index;
push @
{ $self->{attributes
} }, \
%attrib, "";
elsif ( $args[2] > 0 && $args[3] == $max_index ) {
#print STDERR "***Run extends to the end\n";
push @
{ $self->{run_start_index
} }, 0, $args[2];
push @
{ $self->{run_end_index
} }, $args[2] - 1, $max_index;
push @
{ $self->{attributes
} }, "", \
%attrib;
Describe your function here
my ( $self, @args ) = @_;
my $run_start_index = $self->_which_run( $args[2] );
my (@attributes) = ( $args[0], $args[1] );
if ( $run_start_index != 0 ) { # if not in the first run
# fill up the new array with the data upto this point
for ( my $i = 0 ; $i < $run_start_index ; $i++ ) {
push ( @new_start, $self->_get_run_start($i) );
push ( @new_end, $self->_get_run_end($i) );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
my $begin_offset = $args[2];
for ( my $i = $run_start_index ; $i < $self->{runcount
} ; $i++ ) {
my $begin_index = $self->_get_run_start($i);
my $end_index = $self->_get_run_end($i);
if ( $begin_index > $args[3] ) {
push ( @new_start, $begin_index );
push ( @new_end, $end_index );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
elsif ($begin_index == $begin_offset
&& $end_index <= $args[3] )
push ( @new_start, $begin_index );
push ( @new_end, $end_index );
$self->_get_spliced_attributes( $i, @attributes ) );
#print STDERR "***Inside\n";
# update the begin offset to start of the next run
if ( defined $self->_get_run_start( $i + 1 ) ) {
$begin_offset = $self->_get_run_start( $i + 1 );
#print STDERR "Begin offset: $begin_offset\n";
elsif ($begin_index == $begin_offset
&& $end_index > $args[3] )
push ( @new_start, $begin_index, $args[3] + 1 );
push ( @new_end, $args[3], $end_index );
$self->_get_spliced_attributes( $i, @attributes ) );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
elsif ($begin_index < $begin_offset
&& $end_index > $args[3] )
push ( @new_start, $begin_index, $begin_offset, $args[3] + 1 );
push ( @new_end, $begin_offset - 1, $args[3], $end_index );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
$self->_get_spliced_attributes( $i, @attributes ) );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
elsif ($begin_index < $begin_offset
&& $end_index <= $args[3] )
push ( @new_start, $begin_index, $begin_offset );
push ( @new_end, $begin_offset - 1, $end_index );
push ( @new_attrib, ${ $self->{attributes
} }[$i] );
$self->_get_spliced_attributes( $i, @attributes ) );
if ( defined $self->_get_run_start( $i + 1 ) ) {
$begin_offset = $self->_get_run_start( $i + 1 );
#print STDERR "***Runcount: $self->{runcount}","\n";
$self->{run_start_index
} = \
@new_start;
$self->{run_end_index
} = \
@new_end;
$self->{attributes
} = \
@new_attrib;
$self->{runcount
} = @new_start;
#print STDERR "New start: @new_start\n";
#print STDERR "New end : @new_end\n";
#print STDERR "@new_attrib\n";
# if ($args[0] ne "FONT" &&
# $args[0] ne "FAMILY" &&
# $args[0] ne "POSTURE" &&
# $args[0] ne "WEIGHT" &&
# $args[0] ne "UNDERLINE" &&
# $args[0] ne "STRIKETHROUGH" &&
# $args[0] ne "ANCHOR" &&
# $args[0] ne "STROKE_WIDTH" &&
# $args[0] ne "STROKE_COLOR"
# croak "Illegal parameter in Pastel::AttributedString::_check_attribute!\n";
my ( $key, $value ) = @_;
#print STDERR "get_attribute: $key, $value\n";
push @return_array, "font-family";
push @return_array, $value->get_family();
if ( $value->get_style() eq "bold" ) {
push @return_array, "font-weight";
push @return_array, "bold";
elsif ( $value->get_style() eq "bolditalic" ) {
push @return_array, "font-weight", "bold", "font-style", "italic";
elsif ( $value->get_style() eq "italic" ) {
push @return_array, "font-style", "italic";
my $fs = $value->get_size()."pt";
push @return_array, "font-size", $fs;
elsif ( $key eq "COLOR" ) {
push @return_array, "fill", $value->to_hex();
if ( $value->get_alpha() ) {
push @return_array, "fill-opacity", $value->get_alpha();
elsif ( $key eq "FAMILY" ) {
push @return_array, "font-family", $value;
##print STDERR "Entered test\n";
elsif ( $key eq "POSTURE" ) {
if ( $value eq "ITALIC" ) {
push @return_array, "font-style", "italic";
if ( $value eq "OBLIQUE" ) {
push @return_array, "font-style", "oblique";
elsif ( $key eq "WEIGHT" ) {
if ( $value eq "BOLD" ) {
push @return_array, "font-weight", "bold";
elsif ( $key eq "SIZE" ) {
push @return_array, "font-size", $value;
elsif ( $key eq "UNDERLINE" ) {
push @return_array, "text-decoration", "underline";
elsif ( $key eq "STRIKETHROUGH" ) {
push @return_array, "text-decoration", "line-through";
elsif ( $key eq "STROKE_COLOR" ) {
push @return_array, "stroke", $value->to_hex();
if ( $value->get_alpha() ) {
push @return_array, "stroke-opacity", $value->get_alpha();
elsif ( $key eq "STROKE_WIDTH" ) {
push @return_array, "stroke-width", $value;
elsif ( $key eq "ANCHOR" ) {
push @return_array, "text-anchor", lc($value);
# elsif ( $key eq "X" ) {
# push @return_array, "x", $value;
# elsif ( $key eq "Y" ) {
# push @return_array, "y", $value;
# elsif ( $key eq "MULTI_X" ) {
# push @return_array, "x", $value;
# elsif ( $key eq "MULTI_Y" ) {
# push @return_array, "y", $value;
elsif ( $key eq "ROTATE" ) {
push @return_array, "rotate", $value;
# elsif ( $key eq "DX" ) {
# push @return_array, "dx", $value;
# elsif ( $key eq "DY" ) {
# push @return_array, "dy", $value;
# elsif ( $key eq "MULTI_DX" ) {
# push @return_array, "dx", $value;
# elsif ( $key eq "MULTI_DY" ) {
# push @return_array, "dy", $value;
#print STDERR "get_attribute:@return_array\n";
sub _add_to_root_attribute
{
#print STDERR "***@attributes\n";
#my (%root_attribute) = (%{ $self->{root_attribute} });
for ( my $i = 0 ; $i < @attributes ; $i += 2 ) {
${ $self->{root_attribute
} }{ $attributes[$i] } = $attributes[ $i + 1 ];
##print STDERR "Inside loop\n";
#$self->{root_attribute} = \%root_attribute;
#my (@array) = (%{$self->{root_attribure}});
#print STDERR "add_to_root:@array\n";
for ( my $i = 0 ; $i < $self->{runcount
} ; $i++ ) {
if ( $index >= ${ $self->{run_start_index
} }[$i]
&& $index <= ${ $self->{run_end_index
} }[$i] )
my ( $run, $index ) = @_;
if ( $index >= ${ $self->{run_start_index
} }[$run]
&& $index <= ${ $self->{run_end_index
} }[$run] )
if ( $index < $self->{runcount
} ) {
return ${ $self->{run_start_index
} }[$index];
return ${ $self->{run_end_index
} }[$index];
sub _get_root_attributes
{
if ( %{ $self->{root_attribute
} } ) {
#print STDERR "get_root_attributes:***Root attribute present\n";
#print STDERR %{$self->{root_attributes}}, "\n";
#my %attribute_hash = %{$self->{root_attributes}};
foreach my $key ( keys %{ $self->{root_attribute
} } ) {
#print STDERR "get_root_attributes: inside foreach loop\n";
#print STDERR "get_root_attributes: $key:".${$self->{root_attribute}}{$key}."\n";
$self->_get_attribute( $key, ${ $self->{root_attribute
} }{$key} );
#print STDERR "get_root_attributes: @array", "\n";
sub _get_run_attributes
{
if ( ${ $self->{attributes
} }[$index] ) {
my %attribute_hash = %{ ${ $self->{attributes
} }[$index] };
foreach my $key ( keys %attribute_hash ) {
push @array, $self->_get_attribute( $key, $attribute_hash{$key} );
sub _get_run_attributes_as_array
{
my $att = $self->_get_run_attributes($index);
foreach my $key ( keys %{$att} ) {
push @a, $key, ${$att}{$key};
sub _get_spliced_attributes
{
my (@new_attributes) = @_;
#print STDERR "***Inside splice $index\n";
if ( ${ $self->{attributes
} }[$index] ) {
#print STDERR "***Inside splice $index\n";
(%new_hash) = ( %{ ${ $self->{attributes
} }[$index] } );
for ( my $i = 0 ; $i < @new_attributes ; $i += 2 ) {
$new_hash{ $new_attributes[$i] } = $new_attributes[ $i + 1 ];
# foreach my $key (keys %new_hash){
# print STDERR "***Hash key: ".$key.":".$new_hash{$key}."\n";
=head2 _valid_key($string)
Validate the string whether it is a legal C<key> or not. The function
loops through the array in C<valid_keys> attribute and returns true,
if it is present or false it it is not.
Returns : 1 if $string is a valid key or return C<undef>.
my ( $self, $string ) = @_;
foreach my $key ( @
{ $self->{valid_keys
} } ) {
=head2 _get_attributes_as_style()
Describe your function here
sub _get_attributes_as_style
{
my ( $self, @args ) = @_;
for ( my $i = 0 ; $i < @args ; $i += 2 ) {
$s .= $args[$i] . ':' . $args[ $i + 1 ];
if ( $i < ( @args - 2 ) ) {
Malay <curiouser@ccmb.res.in>