Imager::DTP::Letter - letter handling module for Imager::DTP package


Imager-DTP documentation Contained in the Imager-DTP distribution.

Index


Code Index:

NAME

Top

Imager::DTP::Letter - letter handling module for Imager::DTP package

SYNOPSIS

Top

   use Imager::DTP::Letter;

   # first, define font & letter string
   my $font = Imager::Font->new(file=>'path/to/foo.ttf',type=>'ft2',
              size=>16,color=>'#000000',aa=>1);
   my $text = 'A';

   # create instance - basic way
   my $ltr = Imager::DTP::Letter->new();
   $ltr->setText(text=>$text);    # set text
   $ltr->setFont(font=>$font);    # set font
   $ltr->setScale(x=>1.2,y=>0.5); # set transform scale (optional)

   # create instance - or the shorcut way
   my $ltr = Imager::DTP::Letter->new(text=>$text,font=>$font,
             xscale=>1.2,yscale=>0.5);

   # draw letter on target image
   my $target = Imager->new(xsize=>50,ysize=>50);
   $target->box(filled=>1,color=>'#FFFFFF'); # with white background
   $ltr->draw(target=>$target,x=>10,y=>10);

   # and write out image to file
   $target->write(file=>'result.jpg',type=>'jpeg');

DESCRIPTION

Top

Imager::DTP::Letter is a module intended for handling each letter/character in a whole text string (sentence or paragraph). Each Imager::DTP::Letter instance will hold one letter/character internally, and it holds various information about the letter/character, most of it aquired from Imager::Font->bounding_box() method. Thus, Imager::DTP::Letter is intended to act as a single letter with font information (such as ascent/descent) bundled together. It is allowed to set more than one letter/character to a single Imager::DTP::Letter instance, but still, the whole Imager::DTP package will handle the instance as 'single letter'.

METHODS

Top

BASIC METHODS

new

Can be called with or without options.

   use Imager::DTP::Letter;
   my $ltr = Imager::DTP::Letter->new();

   # or perform setText & setFont method at the same time
   my $font = Imager::Font->new(file=>'path/to/foo.ttf',type=>'ft2',
              size=>16);
   my $text = 'A';
   my $ltr  = Imager::DTP::Letter->new(text=>$text,font=>$font);

   # also, can setScale at the same time too.
   my $ltr  = Imager::DTP::Letter->new(text=>$text,font=>$font,
              xscale=>1.2,yscale=>0.5);

setText

Set letter/character to the instance. You must supply some letter/character to text option (it must not be undef or ''). And for multi-byte letter/characters, text must be encoded to utf8, with it's internal utf8-flag ENABLED (This could be done by using utf8::decode() method).



   $ltr->setText(text=>'Z');

   # each time setText is called, previous text will be cleared.
   # like this, internal property will be 'X', not 'ZX'.
   $ltr->setText(text=>'X'); 

setFont

Must supply an Imager::Font object with freetype option (type=>'ft2'). Might work just fine with other font types like type=>'t1' and type=>'tt' too... it's just that I haven't tried yet :P

   my $font = Imager::Font->new(file=>'path/to/foo.ttf',type=>'ft2',
              size=>16);
   $ltr->setFont(font=>$font);

The following Imager::Font options are forced to these values internally. Other options will work fine.

* utf8 => 1
* vlayout => 0

setScale

By setting x and y scaling to ratios other than 1.0 (default setting), you can make letters wider/narrower, or longer/shorter (width and height transformation).

   # make width of letter to 80%
   $ltr->setScale(x=>0.8);

   # make width 120% and height 60%
   $ltr->setScale(x=>1.2,y=>0.6);

Transformation is done by using Imager::Font->transform() method, with the help of Imager::Matrix2d module.

draw

Draw letter/character to the target image (Imager object).

   my $target = Imager->new(xsize=>50,ysize=>50);
   $ltr->draw(target=>$target,x=>10,y=>10);

Imager->String() method is called internally, so you can pass any extra Imager::String options to it by setting in 'others' option.

   # passing Imager::String options
   $ltr->draw(target=>$target,x=>10,y=>10,others=>{aa=>1});

But the following Imager::String options are forced to these values internally, meant for proper result. Other options will work fine.

* utf8 => 1
* vlayout => 0
* align => 0

There is an extra debug option, which will draw a 'letter width x letter ascent' gray box around the letter. Handy for checking the letter's bounding size/position.

   # debug mode
   $ltr->draw(target=>$target,x=>10,y=>10,debug=>1);

GETTER METHODS

Calling these methods will return a property value corresponding to the method name.

getText

Returns the letter/character string.

getFont

Returns a reference (pointer) to the Imager::Font object.

getScale

Returns an array containing the current x/y scale setting.

   my($x,$y) = $self->getScale();

getWidth

getHeight

getAscent

getDescent

getAdvancedWidth

getLeftBearing

getRightBearing

getGlobalAscent

getGlobalDescent

getEndOffset

These returns the designated property value (in pixels) of current letter, when drawn with the current font setting. See Imager::Font::BBox's documentation for description on each properties.

TODO

Top

* change Carp-only error handling to something more elegant.

AUTHOR

Top

Toshimasa Ishibashi, <iandeth99@ybb.ne.jp>

COPYRIGHT & LICENSE

Top

SEE ALSO

Top

Imager, Imager::DTP


Imager-DTP documentation Contained in the Imager-DTP distribution.

package Imager::DTP::Letter;
use strict;
use Carp;
use Imager;
use Imager::Matrix2d;
use vars qw($VERSION);

$VERSION = '0.05';

sub new {
	my $self = shift;
	my %o = @_;
	# define properties
	my $p = {
		text           => '',
		font           => '',
		width          => 0,
		height         => 0,
		descent        => 0,
		ascent         => 0,
		advanced_width => 0,
		left_bearing   => 0,
		right_bearing  => 0,
		global_ascent  => 0,
		global_descent => 0,
		end_offset     => 0,
		xscale         => 1,
		yscale         => 1,
		isUpdated      => 0, # check flag for _calcWidthHeight needs
	};
	$self = bless($p,$self);
	# set properties
	$self->setText(text=>$o{text}) if(defined($o{text}));
	$self->setFont(font=>$o{font}) if(defined($o{font}));
	$self->setScale(x=>$o{xscale},y=>$o{yscale}) if($o{xscale} || $o{yscale});
	return $self;
}

sub draw {
	my($self) = shift;
	my %o = $self->_draw_init(@_);
	# recalculate bounding box
	$self->_calcWidthHeight();
	# draw frame - for debug
	if($o{debug}){
		# real body frame
		$o{target}->box(
			filled=>1,aa=>0,color=>'#FFAA99',
			xmin=>$o{x} + $self->getLeftBearing(),
			ymin=>$o{y} + $self->getGlobalAscent() - $self->getAscent(),
			xmax=>$o{x} + $self->getEndOffset(),
			ymax=>$o{y} + $self->getGlobalAscent() - $self->getDescent(),
		) if($o{debug} > 1);
		# virtual (outer) body frame
		$o{target}->box(
			filled=>0,aa=>0,color=>'#999999',
			xmin=>$o{x},
			ymin=>$o{y},
			xmax=>$o{x} + $self->getAdvancedWidth(),
			ymax=>$o{y} + $self->getGlobalAscent() - $self->getGlobalDescent(),
		);
	}
	# scale transformation
	my($sx,$sy) = $self->getScale();
	if($sx != 1 || $sy != 1){
		my $m = Imager::Matrix2d->scale(x=>$sx,y=>$sy);
		$self->getFont()->transform(matrix=>$m);
	}
	# draw letter - using Imager::String method
	$o{target}->string(
		%{$o{others}},
		x => $o{x} + $self->getLeftBearing(),
		y => $o{y} + $self->getGlobalAscent() - $self->getAscent(),
		text => $self->getText(),
		font => $self->getFont(),
		utf8 => 1, vlayout => 0, align => 0
	) or die $o{target}->errstr;
	# draw baseline position - for debug
	if($o{debug}){
		$o{target}->box(
			filled=>1,aa=>0,color=>'#880000',
			xmin=>$o{x},
			xmax=>$o{x}+$self->getAdvancedWidth(),
			ymin=>$o{y}+$self->getGlobalAscent(),
			ymax=>$o{y}+$self->getGlobalAscent()
		);
	}
	return 1;
}

sub _draw_init {
	my($self) = shift;
	my %o = @_;
	# validation
	if(!defined($self->getFont()) && !defined($self->getText())){
		confess "you must define both text and font before drawing";
	}
	if(ref($o{target}) !~ /^Imager(::.+)?/){
		confess "target must be an Imager Object ($o{target})";
	}
	$o{x} = 0 if(!$o{x});
	$o{y} = 0 if(!$o{y});
	return %o;
}

sub setText {
	my $self = shift;
	my %o  = @_;
	if($o{text} eq ''){
		confess "text: must define some text";
	}
	$self->{text} = $o{text};
	$self->{isUpdated} = 0;
	return 1;
}

sub setFont {
	my $self = shift;
	my %o  = @_;
	if(!defined($o{font}) || ref($o{font}) !~ /^Imager::Font(::.+)?$/){
		confess "font: must supply an Imager::Font Object ($o{font})";
	}
	$o{font}->{utf8} = 1;
	$o{font}->{vlayout} = 0;
	$self->{font} = $o{font};
	$self->{isUpdated} = 0;
	return 1;
}

sub setScale {
	my $self = shift;
	my %o  = @_;
	# validation
	foreach my $v (qw(x y)){
		if($o{$v} && $o{$v} !~ /^\d+(\.\d+)?$/){
			confess "$v: must be a ratio value (like 0.5, 1.2, and so on)";
		}
	};
	$self->{xscale} = $o{x} if($o{x});
	$self->{yscale} = $o{y} if($o{y});
	$self->{isUpdated} = 0;
	return 1;
}

sub _calcWidthHeight {
	my $self = shift;
	return undef if($self->{isUpdated});
	return undef if($self->getText() eq '' || !$self->getFont());
	my %o  = @_;
	# validation
	foreach my $v (keys %o){
		if($o{$v} !~ /^\d+$/){
			confess "$v: must be an integer ($o{$v})";
		}
	}
	my $f = $self->getFont();
	my $b = $f->bounding_box(string=>$self->getText());
	unless(defined($b->ascent)){
		confess qq(unable to map string '$self->getText()' with the specified font.
		           		           Perhaps you forgot to encode your text to utf8?
		           		           *ATTENTION* utf8-flag must be enabled! try using \&utf8::decode() );
	}
	my ($x,$y) = $self->getScale();
	$self->{width}          = $b->total_width();
	$self->{height}         = $b->text_height();
	$self->{descent}        = $b->descent();
	$self->{ascent}         = $b->ascent();
	$self->{advanced_width} = $b->advance_width();
	$self->{left_bearing}   = $b->left_bearing;
	$self->{right_bearing}  = $b->advance_width() - $b->end_offset();
	$self->{global_ascent}  = $b->global_ascent();
	$self->{global_descent} = $b->global_descent();
	$self->{end_offset}     = $b->end_offset();
	if($x != 1){
		foreach my $v qw(width advanced_width left_bearing right_bearing end_offset){
			$self->{$v} *= $x;
		}
	}
	if($y != 1){
		foreach my $v qw(height descent ascent global_ascent global_descent){
			$self->{$v} *= $y;
		}
	}
	# for blank space
	if($self->{text} eq ' '){
		$self->{ascent} = $self->{width};
		$self->{height} = $self->{width};
	}
	$self->{isUpdated} = 1;
	return 1;
}

sub getText {
	return shift->{text};
}
sub getFont {
	return shift->{font};
}
sub getScale {
	my $self = shift;
	return ($self->{xscale},$self->{yscale});
}
sub getWidth {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{width};
}
sub getHeight {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{height};
}
sub getAscent {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{ascent};
}
sub getDescent {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{descent};
}
sub getAdvancedWidth {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{advanced_width};
}
sub getLeftBearing {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{left_bearing};
}
sub getRightBearing {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{right_bearing};
}
sub getGlobalAscent {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{global_ascent};
}
sub getGlobalDescent {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{global_descent};
}
sub getEndOffset {
	my $self = shift;
	$self->_calcWidthHeight();
	return $self->{end_offset};
}

1;
__END__