#!/usr/bin/perl -w
#############################################################################
# Project: Web-Utilities
# Descr:   Create an button image usable e.g. in menus
# Author:  Dr. Jürgen Vollmer <Juergen.Vollmer@informatik-vollmer.de>
# Id: create-button,v 1.3 2004/03/04 09:08:56 vollmer Exp $
#############################################################################

require 5.008;
=pod

=head1 NAME

create-button - a script to create button images used in web pages

=head1 SYNOPSIS

create-button [<options>]* text ...

=head1 OPTIONS

=over

=item B<--output> I<file>

Write output to the given file (default: display it).
"-" indicates the PNG data are written to stdout.
The generated graphics format is determined by the given
file suffix. See L<convert>(1) for the list of acceptable
picture formats.
If no output file is specified the image is shown using the
L<display>(1) routine.

=item B<--font> I<file>

Use true-type font specified by I<file> (default Arial bold italics).

=item B<--size> I<points>

Render text in the give size (default 14).

=item B<--foreground> I<color>

=item B<--fg> I<color>

The foreground color of the text, default black.

=item B<--background> I<color>

=item B<--bg> I<color>

The background color of the image, default white.

=item B<--center>

Center the rendered text (default).

=item B<--left>

Left adjust the rendered text.

=item B<--right>

Right adjust the rendered text.

=item B<--top>

Place the text at the top of the image.

=item B<--middle>

Place the text in the middle of the image (default).

=item B<--bottom>

Place the text at the bottom of the image.

Right adjust the rendered text.

=item B<--rotate> I<angle>

Rotate the text -360 <= I<angle> <= 360 (default 0).

Right adjust the rendered text.

=item B<-W>  I<pixel>

=item B<--width>  I<pixel>

Width of the image.

=item B<-H> I<pixel>

=item B<--height> I<pixel>

Height of the image.

=item B<--Raise> I<width>xI<height>

Raises the image into 3D.
(Not available, if written to stdout).

=item B<--raise>

Just an abbreviation for B<--Raise> I<5x5>

=item B<--Show>

Show the size of the created picture and text.

=item B<-h>

=item B<--help>

Print a brief help message and exits.

=item B<-M>

=item B<--manual>

Prints the manual page and exits.

=item B<--debug>

Enable debugging.

=back

Options names may be abbreviated to uniqueness. Single letter options may be
used with only one dash.

Colors may be given as RGB triples: I<red>I<green>I<blue>
where I<red>, I<green> and I<blue> are hexadecimal values:
E.g. I<FFFFFF> means black and I<0> means white.
Those color values may be separated by a , (comma) or : (colon).
E.g. I<FF,FF,FF> or I<0,0,0>.

If no height or width is given, the width and height of the button text will
be used.

=head1 DESCRIPTION

create-button is a simple script to create images containing some text.

=head1 EXAMPLE

 create-button -bg 9933CC -fg FFFFFF -size 20 test and more tests

displays a button containing the text "test and more tests".

 create-button -bg 9933CC -fg FFFFFF -rotate 10 -raise -o test.gif test

creates the file test.gif containing an image with the text "test"
which is rotated to 10 degrees and the entire image is raised.

=head1 REQUIREMENTS

The GD perl package and the ImageMagik tools.

=head1 AUTHOR

Dr. Juergen Vollmer <Juergen.Vollmer@informatik-vollmer.de>

If you find this software useful, I would be glad to receive a postcard
from you, showing the place where you're living.

=head1 HOMEPAGE

http://www.informatik-vollmer.de/software/create-button.html

=head1 COPYRIGHT

Copyright (C) 2004 Dr. Juergen Vollmer, Viktoriastrasse 15,
D-76133 Karlsruhe, Germany.

=head1 LICENSE

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston,
MA  02111-1307  USA

=head1 VERSION
1.2

=cut

use strict;
use English;
use Getopt::Long;
use File::Basename;
use Pod::Usage;
use Math::Trig;
use GD;

our $CMD            = basename ($0);
our ($VERSION)	   = 'Revision: 1.3 $' =~ '(\d*\.\d*)';
our ($VERSION_DATE) = 'Date: 2004/03/04 09:08:56 $' =~ '(\d*/\d*/\d*)';

our (	$option_output,
	$option_font,
	$option_font_size,
	$option_fg,
	$option_bg,
	$option_text_angle,
	$option_width,
	$option_height,
	$option_align_x,
	$option_align_y,
	$option_raise,
	$option_show_size,
	$option_help,
	$option_manual,
	$option_debug);

#############################################################################
## functions
#############################################################################

sub error
{
    die "$CMD error: " . join (" ", @_) . "\n";
}

#############################################################################

sub debug
{
    my $format = shift;
    ($option_debug) && printf STDERR "$format", @_;
}

#############################################################################

sub do_output($)
# output the generated image
# Arguments: $image  an GD-object
{
    my $image = shift;
    my $convert = "| convert"; $convert .= " -raise $option_raise" if ($option_raise);
    if (defined $option_output) {
	if ($option_output eq "-") {
	    # write to a STDOUT
	    binmode STDOUT;
	    print STDOUT $image->png;
	} else {
	    # write to a file
	    open (F, "$convert - $option_output") ||
		error "can not call \"convert $option_output\"";
	    binmode F;
	    print F $image->png;
	    close F;
	}
    } else {
	# display it
	open (F,"$convert - -| display -") ||
	    error "can not call \"display\"";
	binmode F;
	print F $image->png;
        close F;
    }
}

#############################################################################

sub size_of($)
# compute height and width of the given text, and the hight of the first
# letter. The hight of the first letter is diffrent from the entire hight,
# if the text contains a character with unter-length, i.e. one of: gjpqy
# Arguments:   $text
# Returns:     ($width, $height, $hight_first)
{
    my $text   = shift;
    my $image  = new GD::Image;
    my $black  = $image->colorAllocate(255,255,255);
    my ($ll_x, $ll_y, # lower left  egde
	$lr_x, $lr_y, # lower right edge
	$ul_x, $ul_y, # upper left  edge
	$ur_x, $ur_y) = $image->stringFT ($black,
					  $option_font,
					  $option_font_size,
					  $option_text_angle, 0, 0, $text);
    defined $ll_x || error ("can not render $text: $@");
    my $width  = $lr_x - $ll_x;
    my $height = $ll_y - $ul_y;

    my $height_first;
    if ($text =~ /[gjpqy]/) {
	# text contains letter with under-length
	my $letter = substr ($text, 0,1);

	# text contains text with upper-length, e.g:
	if ($text =~ /[bdfhijkltöäüßÖÄÜA-Z0-9]/) {
	    # use "A" as canonical letter for the hight_first computation
	    $letter = "A";
	}

	($ll_x, $ll_y, # lower left  egde
	 $lr_x, $lr_y, # lower right edge
	 $ul_x, $ul_y, # upper left  edge
	 $ur_x, $ur_y) = $image->stringFT ($black,
					   $option_font,
					   $option_font_size,
					   $option_text_angle, 0, 0,
					   $letter);
	$height_first = $ll_y - $ul_y;
    } else {
	$height_first = $height;
    }

    debug ("size: width = %3d, height = %3d, height_first = %3d \n",
	   $width, $height, $height_first);
    return ($width, $height, $height_first);
}

#############################################################################

sub do_render(@)
# render the given text
# Arguments:   $text
# Returns:     $image   a GD-object
{
    my $text  = join (" ", @_);
    my ($width, $height, $height_first) = size_of ($text);

    $option_width  = $width  if (!defined $option_width);
    $option_height = $height if (!defined $option_height);

    if ($option_show_size) {
	printf "$CMD: text:    width = %3d height = %d\n",
	       $width, $height;
	printf "$CMD: picture: width = %3d height = %d\n",
	       $option_width, $option_height;
    }

    my $image = new GD::Image($option_width, $option_height);

    # allocate some colors
    my ($bg_r, $bg_g, $bg_b) = (($option_bg >> 16) & 0xFF,
				($option_bg >>  8) & 0xFF,
				 $option_bg        & 0xFF);
    my ($fg_r, $fg_g, $fg_b) = (($option_fg >> 16) & 0xFF,
				($option_fg >>  8) & 0xFF,
				 $option_fg        & 0xFF);

    my $bg = $image->colorAllocate($bg_r, $bg_g, $bg_b);
    my $fg = $image->colorAllocate($fg_r, $fg_g, $fg_b);

    $image->filledRectangle(0, 0,
			    $option_width, $option_height,
			    $bg);

    my ($x, $y);
    my $offset = 2; # offset to one of the borders
    if ($option_raise) {
	$option_raise =~ /^(\d+)x\d+$/;
	$offset += $1;
    }
    if ($option_align_x eq "center") {
	$x = ($option_width - $width)  / 2;
    } elsif ($option_align_x eq "left") {
	$x = $offset;
    } else { # right
	$x = $option_width - $width - $offset;
    }

    if ($option_align_y eq "middle") {
	$y  = ($option_height + $height) / 2;
    } elsif ($option_align_y eq "top") {
	$y = $height + $offset;
    } else { # bottom
	$y  = $option_height - $offset;
    }
    $y -= $height - $height_first;

    debug ("align_x     = $option_align_x; align_y = $option_align_y\n");
    debug ("pos:  x     = %3d, y      = %3d\n", $x, $y);
    debug ("rec:  x     = %3d, y      = %3d\n", $option_width, $option_height);

    $image->stringFT ($fg, $option_font, $option_font_size,
		      $option_text_angle,
		      $x, $y, $text);
    return $image;
}

#############################################################################

#############################################################################
## check command line options and arguments
#############################################################################

Getopt::Long::Configure ("auto_abbrev",    "no_ignore_case",
			 "getopt_compat",  "require_order");

$option_font         = "/usr/X11R6/lib/X11/fonts/truetype/arialbi.ttf";
$option_font_size    = 14;
$option_text_angle   = 0; # in rad
$option_align_x      = "center";
$option_align_y      = "middle";
$option_fg           = "0,0,0";     # black
$option_bg           = "FF,FF,FF";  # white

GetOptions("output=s"              => \$option_output,
	   "foreground=s"          => \$option_fg,
	   "fg=s"                  => \$option_fg,
	   "background=s"          => \$option_bg,
	   "bg=s"                  => \$option_bg,
	   "font=s"                => \$option_font,
	   "size=i"                => \$option_font_size,
	   "rotate=i"		   => \$option_text_angle,
	   "width|W=i"             => \$option_width,
	   "height|H=i"            => \$option_height,
	   "Raise=s"               => \$option_raise,
	   "raise"                 => sub {$option_raise   = "5x5";   },
	   "center"                => sub {$option_align_x = "center";},
	   "left"                  => sub {$option_align_x = "left";  },
	   "right"                 => sub {$option_align_x = "right"; },
	   "top"                   => sub {$option_align_y = "top";   },
	   "middle"                => sub {$option_align_y = "middle";},
	   "bottom"                => sub {$option_align_y = "bottom";},
	   "Show"                  => \$option_show_size,
	   "help|h"                => \$option_help,
	   "manual|M"              => \$option_manual,
	   "debug"                 => \$option_debug,
	   ) or pod2usage(-exitval => 2, -verbose => 0,
			  -message => "Try --help or --man.");

pod2usage (-exitval => 1, -verbose => 1) if ($option_help);
pod2usage (-exitval => 0, -verbose => 2) if ($option_manual);
pod2usage (-exitval => 2, -verbose => 0,
	   -message => "$CMD: No arguments given.\n") if ($#ARGV < 0);

# make it a decimal value
$option_bg =~ s/[,:]//g;
$option_fg =~ s/[,:]//g;
$option_bg = hex ($option_bg);
$option_fg = hex ($option_fg);

# convert degree to radians
$option_text_angle = deg2rad($option_text_angle);

[ -f $option_font ] || error "no such font file: $option_font";

do_output (do_render (@ARGV));

#############################################################################
# Log: create-button,v $
# Revision 1.3  2004/03/04 09:08:56  vollmer
# Added automatic computation of the size of the picture (by not setting the
# default hight and width).
# Take care of under-length of charcters like "g" (quite a simple heuristics).
#
# Revision 1.2  2004/02/15 11:37:23  vollmer
# Insert a small offset for -left/-right/-bottom/-top and -raise in those
# cases.
#
# Revision 1.1  2004/02/14 17:13:24  vollmer
# Initial revision
#
#############################################################################
