/usr/local/CPAN/Image-WMF/Image/WMF/Image.pm
package Image::WMF::Image;
# Copyright 2001 Tony Cox. See accompanying README file for
# usage information
use strict;
use Image::WMF::Constants;
use Image::WMF::Colour;
use Image::WMF::Font;
use Image::WMF::Rectangle;
use Image::WMF::Polygon;
use vars qw($VERSION @ISA @EXPORT $AUTOLOAD $DEBUG);
$VERSION = "1.00";
#$DEBUG = 1;
sub new {
my ($class, $filename) = @_;
my $obj = bless {
_filename => $filename,
_handles => [],
_word => [],
_maxobjectsize => 0,
_wmf => '',
_header => '',
_fhandle => undef,
}, $class;
$obj->_initialise($filename);
return $obj;
}
sub _initialise {
my ($self, $filename) = @_;
if (defined $filename){
$self->{'_filename'} = $filename;
$self->createFile();
}
$self->escape("PerlWMF, v$VERSION (c) Tony Cox, 2003");
}
sub translateFontName {
my ($self,$fontname) = @_;
if (defined $FONTNAME{lc($fontname)}){
return($FONTNAME{lc($fontname)});
} else {
print STDERR "Unsupported font (\"$fontname\") using Arial\n";
return ($FONTNAME{'arial'});
}
}
sub setTranslateFontNames {
my ($self) = shift;
%FONTNAME = @_;
}
sub getTranslateFontNames {
my ($self) = shift;
return (\%FONTNAME);
}
sub createFont {
my ($self,$font,$escapement,$is_underline,$is_strikeout) = @_;
unless ($font->isa("Font")){
die("Invalid font object in createFont!");
}
my $c = $self->toByte(0x0190);
if($font->isBold()){
my $c = $self->toByte(0x01BC);
}
return($self->createFontIndirect(
$font->getSize(),0,$escapement,0,$c, #int
$font->isItalic(),$is_underline,$is_strikeout, #bool
$self->toByte(0),$self->toByte(0),$self->toByte(0), #byte
$self->toByte(0),$self->toByte(0), #byte
$self->translateFontName($font->getFace())) #string
)
}
sub createFontIndirect {
my ($self,$i,$j,$k,$l,$i1, #int
$is_italic,$is_underline,$is_strikeout, #bool
$charset,$outprecision,$clipprecision,$quality,$pitchandfamily, #byte
$facename) = @_; #string
$self->metaRecord(763,9 + (length($facename)+2)/2);
$self->writeWord($i);
$self->writeWord($j);
$self->writeWord($k);
$self->writeWord($l);
$self->writeWord($i1);
my $j1 = 0;
if($is_italic){
$j1=1;
}
if($is_underline){
$j1+=256;
}
$self->writeWord($j1);
$j1 = $charset << 8 & 0xFF00;
if($is_strikeout){
$j1++;
}
$self->writeWord($j1);
$self->writeWord($outprecision | $clipprecision << 8 & 0xFF00);
$self->writeWord($quality | $pitchandfamily << 8 & 0xFF00);
my @abytes0 = ();
my @text = split(//,reverse($facename));
#print STDERR "Packing string: \"$facename\"\n";
while (@text){
my $c = shift(@text);
$c = $self->toByte($c);
unshift (@abytes0, $c);
}
unshift (@abytes0, $self->toByte(0)); # add padding byte
for (my $k=0;$k < scalar(@abytes0)/2;$k++){
if (!defined $abytes0[$k*2+1]){
$self->writeWord($abytes0[$k*2] | $self->toByte(0) << 8 & 0xFF00);
} else {
$self->writeWord($abytes0[$k*2] | $abytes0[$k*2+1] << 8 & 0xFF00);
}
}
my $h = $self->addHandle();
print STDERR "Added Font handle at stack position: $h\n" if $DEBUG;
return($h);
}
sub writeColour {
my ($self, $c) = @_;
$self->writeInteger(
$c->red() & 0xFF
| $c->green() << 8 & 0xFF00
| $c->blue() << 16 & 0xFF0000
);
#print STDERR "Wrote packed RGB colour structure: ";
#print STDERR $c->red()," ",$c->green()," ",$c->blue(),"\n";
}
sub createPenIndirect {
my ($self, $i, $j, $c) = @_;
$self->metaRecord(762,5);
$self->writeWord($i);
$self->writeInteger($j);
$self->writeColour($c); # need to write a color object
my $h = $self->addHandle();
print STDERR "Added PenIndirect handle at stack position: $h\n" if $DEBUG;
return ($h);
}
sub createBrushIndirect {
my ($self, $i, $c, $j) = @_;
$self->metaRecord(764,4);
$self->writeWord($i);
$self->writeColour($c); # need to write a color object
$self->writeWord($j);
my $h = $self->addHandle();
print STDERR "Added BrushIndirect handle at stack position: $h\n" if $DEBUG;
return ($h);
}
sub roundRect {
my ($self, $i, $j, $k, $l, $m, $n) = @_;
$self->metaRecord(1564,6);
$self->writeWord($n);
$self->writeWord($m);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub pie {
my ($self, $i, $j, $k, $l, $m, $n, $o, $p) = @_;
$self->metaRecord(2078,8);
$self->writeWord($p);
$self->writeWord($o);
$self->writeWord($n);
$self->writeWord($m);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub arc {
my ($self, $i, $j, $k, $l, $m, $n, $o, $p) = @_;
$self->metaRecord(2071,8);
$self->writeWord($p);
$self->writeWord($o);
$self->writeWord($n);
$self->writeWord($m);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub chord {
my ($self, $i, $j, $k, $l, $m, $n, $o, $p) = @_;
$self->metaRecord(2096,8);
$self->writeWord($p);
$self->writeWord($o);
$self->writeWord($n);
$self->writeWord($m);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub ellipse {
my ($self, $i, $j, $k, $l) = @_;
$self->metaRecord(1048,4);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub rectangle {
my ($self, $i, $j, $k, $l) = @_;
$self->metaRecord(1051,4);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub setPolyFillMode {
my ($self, $i) = @_;
$self->metaRecord(262,1);
$self->writeWord($i);
}
sub setMapMode {
my ($self, $i) = @_;
$self->metaRecord(259,1);
$self->writeWord($i);
}
sub setROP2 {
my ($self, $i) = @_;
$self->metaRecord(260,1);
$self->writeWord($i);
}
sub setBKMode {
my ($self, $i) = @_;
$self->metaRecord(258,1);
$self->writeWord($i);
}
sub setBKColour {
my ($self, $c) = @_;
$self->metaRecord(513,2);
$self->writeColour($c);
}
sub setTextColour {
my ($self, $c) = @_;
$self->metaRecord(521,2);
$self->writeColour($c);
}
sub setTextAlign {
my ($self, $i) = @_;
$self->metaRecord(302,1);
$self->writeWord($i);
}
sub setTextCharacterExtra {
my ($self, $i) = @_;
$self->metaRecord(264,1);
$self->writeWord($i);
}
sub setStretchBltMode {
my ($self, $i) = @_;
$self->metaRecord(263,1);
$self->writeWord($i);
}
sub setTextJustification {
my ($self, $i, $j) = @_;
$self->metaRecord(522,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub setPixel {
my ($self, $i, $j, $c) = @_;
$self->metaRecord(1055,4);
$self->writeColour($c);
$self->writeWord($j);
$self->writeWord($i);
}
sub floodFill {
my ($self, $i, $j, $c) = @_;
$self->metaRecord(1049,4);
$self->writeColour($c);
$self->writeWord($j);
$self->writeWord($i);
}
sub extFloodFill {
my ($self, $i, $j, $c, $k) = @_;
$self->metaRecord(1352,5);
$self->writeWord($k);
$self->writeColour($c);
$self->writeWord($j);
$self->writeWord($i);
}
sub lineTo {
my ($self, $i, $j) = @_;
$self->metaRecord(531,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub moveTo {
my ($self, $i, $j) = @_;
$self->metaRecord(532,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub setWindowOrg {
my ($self, $i, $j) = @_;
$self->metaRecord(523,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub setViewportOrg {
my ($self, $i, $j) = @_;
$self->metaRecord(525,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub offsetViewportOrg {
my ($self, $i, $j) = @_;
$self->metaRecord(529,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub setViewportExt {
my ($self, $i, $j) = @_;
$self->metaRecord(526,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub scaleWindowExt {
my ($self, $i, $j, $k, $l) = @_;
$self->metaRecord(1024,4);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub scaleViewportExt {
my ($self, $i, $j, $k, $l) = @_;
$self->metaRecord(1042,4);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub patBlt {
my ($self, $i, $j, $k, $l, $m) = @_;
$self->metaRecord(1565,4);
$self->writeInteger($m);
$self->writeWord($l);
$self->writeWord($k);
$self->writeWord($j);
$self->writeWord($i);
}
sub offsetWindowOrg {
my ($self, $i, $j) = @_;
$self->metaRecord(527,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub setWindowExt {
my ($self, $i, $j) = @_;
$self->metaRecord(524,2);
$self->writeWord($j);
$self->writeWord($i);
}
sub polygon {
my ($self, $arrayref, $arrayref2, $i) = @_;
$self->metaRecord(804,1+2*$i);
$self->writeWord($i);
for (my $k=0;$k<$i;$k++){
$self->writeWord($arrayref->[$k]);
$self->writeWord($arrayref2->[$k]);
}
}
sub polyline {
my ($self, $arrayref, $arrayref2, $i) = @_;
$self->metaRecord(805,1+2*$i);
$self->writeWord($i);
for (my $k=0;$k<$i;$k++){
$self->writeWord($arrayref->[$k]);
$self->writeWord($arrayref2->[$k]);
}
}
# Add a WMF comment to the file.
# Some byte fixing required here. Have to pad with a null
# byte if using a string with odd number of characters??
sub escape {
my ($self, $text) = @_;
my @abytes0 = ();
my @text = split(//,reverse($text));
print STDERR "Embedded comment: \"$text\" (",length($text)," chars)\n" if $DEBUG;
while (@text){
my $c = shift(@text);
$c = $self->toByte($c);
unshift (@abytes0, $c);
}
if(length($text) % 2 == 0){
unshift (@abytes0, $self->toByte(0)); # add padding byte
print STDERR "Adding padding byte\n" if $DEBUG;
}
$self->metaRecord(1574, 2+(scalar(@abytes0)+1)/2);
$self->writeWord(scalar(@abytes0));
my $newArrayLen = ((scalar(@abytes0)+1)/2)*2;
$self->writeWord(length($text)/2);
for (my $j=0;$j < $newArrayLen;$j+=2){
if (!defined $abytes0[$j+1]){
$self->writeWord($abytes0[$j] | $self->toByte(0) << 8 & 0xFF00);
} else {
$self->writeWord($abytes0[$j] | $abytes0[$j+1] << 8 & 0xFF00);
}
}
}
sub textOut {
my ($self, $i, $j, $text) = @_;
$self->metaRecord(1313, 3+(length($text)+1)/2);
$self->writeWord(length($text));
my @abytes0 = ();
my @text = split(//,reverse($text));
print STDERR "Packing string: \"$text\"\n" if $DEBUG;
while (@text){
my $c = shift(@text);
$c = $self->toByte($c);
unshift (@abytes0, $c);
}
for (my $k=0;$k < scalar(@abytes0)/2;$k++){
my $l = 0;
if (!defined $abytes0[$k*2+1]){
$l = $abytes0[$k*2] | $self->toByte(0) << 8 & 0xFF00;
} else {
$l = $abytes0[$k*2] | $abytes0[$k*2+1] << 8 & 0xFF00;
}
$self->writeWord($l);
}
$self->writeWord($j);
$self->writeWord($i);
}
sub extTextOut {
my ($self, $i, $j, $k, $rect, $text, $spacing_arrayref) = @_;
my @spaces = ();
if (defined ($spacing_arrayref)){
print STDERR "Setting spacing array\n" if $DEBUG;
@spaces = @$spacing_arrayref;
}
my $l = 4 + (length($text)+1)/2;
if ($k != 0){
$l += 4;
}
if (defined $spacing_arrayref){
$l += length($text);
}
$self->metaRecord(2610,$l);
$self->writeWord($j);
$self->writeWord($i);
$self->writeWord(length($text));
$self->writeWord($k);
if($k != 0){
$self->writeWord($rect->x());
$self->writeWord($rect->y());
$self->writeWord($rect->width());
$self->writeWord($rect->height());
}
my @abytes0 = ();
my @text = split(//,reverse($text));
#print STDERR "Packing string: \"$text\"\n";
while (@text){
my $c = shift(@text);
$c = $self->toByte($c);
unshift (@abytes0, $c);
}
print STDERR "Packing ",scalar(@abytes0)/2," bytes\n" if $DEBUG;
for (my $k=0;$k < scalar(@abytes0)/2;$k++){
my $l = 0;
$l = @abytes0[$k*2] | @abytes0[$k*2+1] << 8 & 0xFF00;
$self->writeWord($l);
}
if ($spacing_arrayref){
for (my $k=0;$k < length($text);$k++){
$self->writeWord($spaces[$k]);
}
}
}
sub selectObject {
my ($self, $i) = @_;
my $handles = $self->{'_handles'};
if ($i < scalar(@$handles) && $handles->[$i] == 1){
$self->metaRecord(301, 1);
$self->writeWord($i);
return;
} else {
die "GDI object handle (select) exception: array out of bounds ($i)\n"
}
}
sub deleteObject {
my ($self, $i) = @_;
my $handles = $self->{'_handles'};
if ($i < scalar(@$handles) && $handles->[$i] == 1){
$self->metaRecord(496, 1);
$self->writeWord($i);
$handles->[$i] = 0;
print STDERR "Removed GDI object handle at stack position: $i\n" if $DEBUG;
return;
} else {
die "GDI object handles (delete) exception: can't remove object $i\n"
}
}
sub deleteObjects {
my ($self) = @_;
my $handles = $self->{'_handles'};
for (my $i=0;$i < scalar(@$handles);$i++){
if($handles->[$i] == 1){
$self->deleteObject($i);
print STDERR "GDI handle stack cleanup: $i\n" if $DEBUG;
}
}
}
sub addHandle {
my ($self) = @_;
my $handles = $self->{'_handles'};
for (my $i=0; $i < scalar(@$handles); $i++){
if ($handles->[$i] == 0){
$handles->[$i] = 1;
return($i)
}
}
push (@$handles,1);
return (scalar(@$handles) - 1);
}
sub metaRecord {
my ($self, $i, $j) = @_;
my $k = $j + 3;
$self->writeInteger($k);
$self->writeWord($i);
$self->maxObjectSize($k);
}
sub maxObjectSize {
my ($self, $i) = @_;
if ($i > $self->{'_maxobjectsize'}){
$self->{'_maxobjectsize'} = $i;
}
}
sub getBodySize {
my ($self) = @_;
return (length($self->{'_wmf'})/2);
}
sub makePlaceableHeader {
my ($self,$i,$j,$k,$l,$i1) = @_;
$self->outputHeaderInteger(0x9ac6cdd7);
$self->outputHeaderWord(0);
$self->outputHeaderWord($i);
$self->outputHeaderWord($j);
$self->outputHeaderWord($k);
$self->outputHeaderWord($l);
$self->outputHeaderWord($i1);
$self->outputHeaderInteger(0);
$self->outputHeaderWord($self->calcChecksum($i1,$i,$j,$k,$l));
$self->makeHeader();
}
sub makeHeader {
my ($self) = @_;
$self->outputHeaderWord(1);
$self->outputHeaderWord(9);
$self->outputHeaderWord(768);
$self->outputHeaderInteger($self->getBodySize()+9);
$self->outputHeaderWord(scalar(@{$self->{'_handles'}}));
$self->outputHeaderInteger($self->{'_maxobjectsize'});
$self->outputHeaderWord(0);
print STDERR "Header words: ", $self->getBodySize(), "\n" if $DEBUG;
print STDERR "GDI stack size: ", scalar(@{$self->{'_handles'}}), "\n" if $DEBUG;
}
sub calcChecksum {
my ($self, $i, $j, $k, $l, $i1) = @_;
my $j1 = 39622;
$j1 ^= 0xcdd7;
$j1 ^= $i;
$j1 ^= $j;
$j1 ^= $k;
$j1 ^= $l;
$j1 ^= $i1;
return $j1;
}
sub writeBody {
my ($self) = @_;
if (defined $self->{'_fhandle'}){
my $fh = $self->{'_fhandle'};
print $fh $self->{'_wmf'};
$self->closeFile($self->{'_filename'})
} else {
die "Tried to print WMF body to a non-existent file handle!\n";
}
}
sub writeHeader {
my ($self) = @_;
$self->makeHeader();
if (defined $self->{'_fhandle'}){
my $fh = $self->{'_fhandle'};
print $fh $self->{'_header'};
} else {
die "Tried to print WMF header to a non-existent file handle!\n";
}
}
sub writePlaceableHeader {
my ($self,$i,$j,$k,$l,$i1) = @_;
$self->makePlaceableHeader($i,$j,$k,$l,$i1);
if (defined $self->{'_fhandle'}){
my $fh = $self->{'_fhandle'};
print $fh $self->{'_header'};
} else {
die "Tried to print WMF header to a non-existent file handle!\n";
}
}
sub finalise {
my ($self) = @_;
$self->deleteObjects(); # clean up undeleted GDI objects
$self->metaRecord(0,0);
}
sub toByte {
my ($self, $byte) = @_;
$byte = unpack("C*", $byte);
return($byte);
}
sub loWord {
my ($self, $word) = @_;
return ($word & 0xFFFF)
}
sub hiWord {
my ($self, $word) = @_;
return ($word & 0xFFFF0000) >> 16;
}
sub writeWord {
my ($self, $int) = @_;
$self->outputWord($int);
}
sub writeHeaderWord {
my ($self, $int) = @_;
$self->outputHeaderWord($int);
}
sub outputWord {
my ($self, $int) = @_;
$self->{'_wmf'} .= pack ("C", ($int & 0xFF));
$self->{'_wmf'} .= pack ("C", (($int & 0xFF00)>>8));
}
sub outputHeaderWord {
my ($self, $int) = @_;
$self->{'_header'} .= pack ("C", ($int & 0xFF));
$self->{'_header'} .= pack ("C", (($int & 0xFF00)>>8));
}
sub outputInteger {
my ($self, $int) = @_;
$self->outputWord($self->loWord($int));
$self->outputWord($self->hiWord($int));
}
sub outputHeaderInteger {
my ($self, $int) = @_;
$self->outputHeaderWord($self->loWord($int));
$self->outputHeaderWord($self->hiWord($int));
}
sub writeInteger {
my ($self, $int) = @_;
$self->writeWord($self->loWord($int));
$self->writeWord($self->hiWord($int));
}
sub writeHeaderInteger {
my ($self, $int) = @_;
$self->writeHeaderWord($self->loWord($int));
$self->writeHeaderWord($self->hiWord($int));
}
sub wmf {
my ($self, $x, $y, $resolution) = @_;
# $resolution is number of metafile units per inch (optional argument)
if (defined $resolution){
$self->makePlaceableHeader(0,0,$x,$y,$resolution);
} else {
$self->makePlaceableHeader(0,0,$x,$y,96);
}
return($self->{'_header'} . $self->{'_wmf'});
}
sub createFile {
my ($self, $filename) = @_;
if ($filename) {
$self->{'_filename'} = $filename;
open (OUT, ">$filename") or die "Cannot open WMF file: $!\n";
$self->{'_fhandle'} = \*OUT;
} elsif (defined $self->{'_filename'}){
my $filename = $self->{'_filename'};
open (OUT, ">$filename") or die "Cannot open WMF file: $!\n";
$self->{'_fhandle'} = \*OUT;
} else {
die "Trying to open a file with no name!\n";
}
return $self->{'_fhandle'};
}
sub closeFile {
my ($self, $filename) = @_;
if ($filename eq $self->{'_filename'}) {
close($self->{'_fhandle'});
$self->{'_fhandle'} = undef;
return 1;
}
}
1;