The Perl Advent Calendar needs more articles for 2022. Submit your idea today!
#!perl -w
use strict;
use Imager;
use Imager::Matrix2d;
use Getopt::Long;
use constant PI => 4 * atan2(1,1);

# this sample requires Freetype 2.x
$Imager::formats{"ft2"}
  or die "This sample require Freetype 2.x to be configured in Imager\n";

Getopt::Long::Configure("bundling");

my $angle = 30;
my $fg = 'white';
my $bg = 'black';
my $size = 20;
my $rotate;
GetOptions('angle|a=f' => \$angle,
	   'size|s=i' => \$size,
	   'foreground|fg|f=s' => \$fg,
	   'background|bg|b=s' => \$bg,
	   'rotate|r' => \$rotate)
  or usage();

# check for sanity
if ($angle < -45 or $angle > 45) {
  # while values outside this range are valid, the text would be hard
  # to read
  die "--angle is limited to the range -45 through +45\n";
}
elsif ($size < 10) {
  die "--size must be 10 or greater\n";
}

my $fontfile = shift;
my $outfile = shift;
@ARGV
  or usage();
my $text = "@ARGV";

my $angle_rads = $angle * (PI / 180);
my $trans;

# this is the only difference between rotation and shearing: the
# transformation matrix
if ($rotate) {
  $trans = Imager::Matrix2d->rotate(radians => $angle_rads);
}
else {
  $trans = Imager::Matrix2d->shear(x=>sin($angle_rads)/cos($angle_rads));
}

# only the Freetype 2.x driver supports transformations for now
my $font = Imager::Font->new(file=>$fontfile, type=>'ft2')
  or die "Cannot load font $fontfile: ", Imager->errstr, "\n";

$font->transform(matrix=>$trans);

my $bbox = $font->bounding_box(string=>$text, size=>$size);

# these are in font co-ordinates, so y is flipped
my ($left, $miny, $right, $maxy) =
  transformed_bounds($bbox, $trans);

# convert to image relative co-ordinates
my ($top, $bottom) = (-$maxy, -$miny);

my ($width, $height) = ($right - $left, $bottom - $top);

my $img = Imager->new(xsize=>$width, ysize=>$height);

# fill with the background
$img->box(filled=>1, color=>$bg);

# and draw our string in the right place
$img->string(text => $text,
	     color => Imager::Color->new('white'),
	     x => -$left,
	     y => -$top,
	     color => $fg,
	     font => $font,
	     size => $size);

$img->write(file=>$outfile)
  or die "Cannot save $outfile: ",$img->errstr,"\n";

sub transformed_bounds {
  my ($bbox, $matrix) = @_;

  my $bounds;
  for my $point ([ $bbox->start_offset, $bbox->ascent  ],
		 [ $bbox->start_offset, $bbox->descent ],
		 [ $bbox->end_offset,   $bbox->ascent  ],
		 [ $bbox->end_offset,   $bbox->descent ]) {
    $bounds = add_bound($bounds, transform_point(@$point, $matrix));
  }

  @$bounds;
}

sub transform_point {
  my ($x, $y, $matrix) = @_;

  return
    (
     $x * $matrix->[0] + $y * $matrix->[1] + $matrix->[2],
     $x * $matrix->[3] + $y * $matrix->[4] + $matrix->[5]
    );
}

sub add_bound {
  my ($bounds, $x, $y) = @_;

  $bounds or return [ $x, $y, $x, $y ];

  $x < $bounds->[0] and $bounds->[0] = $x;
  $y < $bounds->[1] and $bounds->[1] = $y;
  $x > $bounds->[2] and $bounds->[2] = $x;
  $y > $bounds->[3] and $bounds->[3] = $y;

  $bounds;
}

sub usage {
  print <<EOS;
Usage: $0 [options] fontfile outfile text...
Options:
  --angle <angle> | -a <angle>
    Set the slant angle in degrees, limited to -45 to +45.  Default 30.
  --size <pixels> | -s <angle>
    Set the text size in pixels.  Must be 10 or greater. Default: 20.
  --foreground <color> | --fg <color> | -f <color>
    Set the text foreground color.  Default: white.
  --background <color> | --bg <color> | -b <color>
    Set the image background color.  Default: black
  --rotate | -r
    Rotate instead of shearing.  Default: shear

eg.
  # shear
  $0 -a 45 fontfiles/ImUgly.ttf output.ppm "something to say"
  # rotate at 100 pixel font size, blue foregroune, white background
  $0 -rs 100 -b white -f blue fontfiles/ImUgly.ttf output.ppm Imager
EOS
  exit 1;
}

=head1 NAME

slant_text.pl - sample for drawing transformed text

=head1 SYNOPSIS

  perl slant_text.pl [options] fontfile output text

  Run without arguments for option details.

=head1 DESCRIPTION

This is a sample for drawing transformed text.

It's complicated by the need to create an image to put the text into,
if you have text, a font, and a good idea where it belongs, it's
simple to create the transformation matrix:

  use Imager::Matrix2d;
  # or call another method for shearing, etc
  my $matrix = Imager::Matrix2d->rotate(radians=>$some_angle);

Feed the transformation matrix to the font:

  $font->transform(matrix=>$font);

then draw the text as normal:

  $image->string(string=>$some_text,
                 x => $where_x,
                 y => $where_y,
                 font => $font,
                 size => $size);

But if you do need the bounds, the code above does show you how to do
it.

=head1 FUNCTIONS

=over

=item transformed_bounds

Returns a list of bounds:

  (minx, miny, maxx, maxy)

These are offsets from the text's starting point in font co-ordinates
- so positive y is I<up>.

Note: this returns the bounds of the transformed bounding box, in most
cases the actual text will not be touching these boundaries.

=cut

=back

=head1 AUTHOR

Tony Cook <tonyc@cpan.org>

=head1 REVISION

$Revision$

=head1 SEE ALSO

Imager(1), Imager::Cookbook, Imager::Matrix2d

=cut