=head2 Word wrapping text
-=head2 Rotating text
+=head2 Shearing (slanting) or Rotating text
-=head2 Shearing (slanting) text
+This requires that you have Imager installed with Freetype 2.x support
+installed, and that the font be created using the Freetype 2.x driver,
+for example:
+
+ my $font = Imager::Font->new(file=>$fontfile, type=>'ft2');
+
+First you need a transformation matrix, for shearing that could be:
+
+ my $angle_in_radians = ...;
+ my $tan_angle = sin($angle_rads) / cos($angle_rads);
+ # shear horizontally, supply this as y instead to do it vertically
+ my $matrix = Imager::Matrix2d->shear(x=>$tan_angle);
+
+For rotation that would be:
+
+ my $matrix = Imager::Matrix2d->rotate(radians => $angle_in_radians);
+
+or:
+
+ my $matrix = Imager::Matrix2d->rotate(degrees => $angle_in_degrees);
+
+Feed that to the font object:
+
+ $font->transform(matrix => $matrix);
+
+and draw the text as normal:
+
+ $image->string(string => $text,
+ x => $where_x,
+ y => $where_y,
+ color => $color,
+ font => $font);
+
+See samples/slant_text.pl for a comprehensive example, including
+calculating the transformed bounding box to create an image to fit the
+transformed text into.
=head1 IMAGE TRANSFORMATION
--- /dev/null
+#!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";
+
+=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
+
+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 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=head1 REVISION
+
+$Revision$
+
+=head1 SEE ALSO
+
+Imager(1), Imager::Cookbook, Imager::Matrix2d
+
+=cut