The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
use strict;

# This program demonstrates using Font::FreeType with Image::Magick.
# It uses the font metrics to position glyphs next to each other as
# a typesetting engine would, and renders them both by compositing a
# bitmap of each glyph onto the output image (using the bitmap_magick()
# convenience method) and by drawing the outline using ImageMagick
# drawing functions.

# TODO - use kerning.

use Font::FreeType;
use Image::Magick;
use List::Util qw( sum );

my $text = "\xC2g.";     # 'Ag.', with a circumflex over the 'A'
my $size = 72;
my $dpi = 600;
my $border = 23;

die "Usage: $0 font-filename output-filename.png\n"
  unless @ARGV == 2;

my ($font_filename, $output_filename) = @ARGV;
my $face = Font::FreeType->new->face($font_filename);
$face->set_char_size($size, $size, $dpi, $dpi);

# Find the glyphs of the string.
my @glyphs = map { $face->glyph_from_char_code(ord $_) } split //, $text;

# Work out how big the text will be.
my $width = sum map { $_->horizontal_advance } @glyphs;
$width -= $glyphs[0]->left_bearing;
$width -= $glyphs[-1]->right_bearing;
my $height = $face->height;
$width += $border * 2;
$height += $border * 2;

my $img = Image::Magick->new(size => "${width}x$height");
$img->Read('xc:white');
$img->Set(stroke => '#0000AA');

my $origin_y = -$face->descender + $border;
my ($text_x, $text_y) = (-$glyphs[0]->left_bearing + $border, $origin_y);

my (undef, $adj_base_y) = adjust_position(0, 0);
my (undef, $adj_top_y) = adjust_position(0, $face->ascender);
my (undef, $adj_btm_y) = adjust_position(0, $face->descender);
$img->Draw(primitive => 'line', points => "0,$adj_base_y $width,$adj_base_y",
           stroke => '#FF0000');
$img->Draw(primitive => 'line', points => "0,$adj_top_y $width,$adj_top_y",
           stroke => '#00FF00');
$img->Draw(primitive => 'line', points => "0,$adj_btm_y $width,$adj_btm_y",
           stroke => '#00FF00');

foreach (@glyphs) {
    my ($adj_x, $adj_y) = adjust_position(0, 0);

    my ($bmp_img, $bmp_left, $bmp_top) = $_->bitmap_magick;
    $bmp_img->Modulate(brightness => 23);   # Light grey, not black.
    $img->Composite(image => $bmp_img, compose => 'Difference',
                    x => $adj_x + $bmp_left, y => $adj_y - $bmp_top);

    my $curr_pos;
    $_->outline_decompose(
        move_to => sub {
            my ($x, $y) = @_;
            ($x, $y) = adjust_position($x, $y);
            $curr_pos = "$x,$y";
        },
        line_to => sub {
            my ($x, $y) = @_;
            ($x, $y) = adjust_position($x, $y);
            $img->Draw(primitive => 'line', points => "$curr_pos $x,$y");
            $curr_pos = "$x,$y";
        },
        cubic_to => sub {
            my ($x, $y, $cx1, $cy1, $cx2, $cy2) = @_;
            ($x, $y) = adjust_position($x, $y);
            ($cx1, $cy1) = adjust_position($cx1, $cy1);
            ($cx2, $cy2) = adjust_position($cx2, $cy2);
            $img->Draw(primitive => 'bezier',
                       points => "$curr_pos $cx1,$cy1 $cx2,$cy2 $x,$y");
            $curr_pos = "$x,$y";
        },
    );

    $img->Draw(primitive => 'line', points => "$adj_x,0 $adj_x,$height",
               stroke => '#CCCC00');

    $text_x += $_->horizontal_advance;
}

my ($adj_x, undef) = adjust_position(0, 0);
$img->Draw(primitive => 'line', points => "$adj_x,0 $adj_x,$height",
           stroke => '#CCCC00');

$img->Write($output_filename);


# Y coordinates need to be flipped over, and both x and y adjusted to the
# position of the character.
sub adjust_position
{
    my ($x, $y) = @_;
    $x += $text_x;
    $y = $height - $y - $text_y;
    return ($x, $y);
}

# vi:ts=4 sw=4 expandtab