Blame AFM.pm

Packit 2638f0
# This -*- perl -*-  module is a simple parser for Adobe Font Metrics files.
Packit 2638f0
Packit 2638f0
package Font::AFM;
Packit 2638f0
Packit 2638f0
=head1 NAME
Packit 2638f0
Packit 2638f0
Font::AFM - Interface to Adobe Font Metrics files
Packit 2638f0
Packit 2638f0
=head1 SYNOPSIS
Packit 2638f0
Packit 2638f0
 use Font::AFM;
Packit 2638f0
 $h = new Font::AFM "Helvetica";
Packit 2638f0
 $copyright = $h->Notice;
Packit 2638f0
 $w = $h->Wx->{"aring"};
Packit 2638f0
 $w = $h->stringwidth("Gisle", 10);
Packit 2638f0
 $h->dump;  # for debugging
Packit 2638f0
Packit 2638f0
=head1 DESCRIPTION
Packit 2638f0
Packit 2638f0
This module implements the Font::AFM class. Objects of this class are
Packit 2638f0
initialised from an AFM (Adobe Font Metrics) file and allow you to obtain information
Packit 2638f0
about the font and the metrics of the various glyphs in the font.
Packit 2638f0
Packit 2638f0
All measurements in AFM files are given in terms of units equal to
Packit 2638f0
1/1000 of the scale factor of the font being used. To compute actual
Packit 2638f0
sizes in a document, these amounts should be multiplied by (scale
Packit 2638f0
factor of font)/1000.
Packit 2638f0
Packit 2638f0
The following methods are available:
Packit 2638f0
Packit 2638f0
=over 3
Packit 2638f0
Packit 2638f0
=item $afm = Font::AFM->new($fontname)
Packit 2638f0
Packit 2638f0
Object constructor. Takes the name of the font as argument.
Packit 2638f0
Croaks if the font can not be found.
Packit 2638f0
Packit 2638f0
=item $afm->latin1_wx_table()
Packit 2638f0
Packit 2638f0
Returns a 256-element array, where each element contains the width
Packit 2638f0
of the corresponding character in the iso-8859-1 character set.
Packit 2638f0
Packit 2638f0
=item $afm->stringwidth($string, [$fontsize])
Packit 2638f0
Packit 2638f0
Returns the width of the argument string. The string is
Packit 2638f0
assumed to be encoded in the iso-8859-1 character set.  A second
Packit 2638f0
argument can be used to scale the width according to the font size.
Packit 2638f0
Packit 2638f0
=item $afm->FontName
Packit 2638f0
Packit 2638f0
The name of the font as presented to the PostScript language
Packit 2638f0
C<findfont> operator, for instance "Times-Roman".
Packit 2638f0
Packit 2638f0
=item $afm->FullName
Packit 2638f0
Packit 2638f0
Unique, human-readable name for an individual font, for instance
Packit 2638f0
"Times Roman".
Packit 2638f0
Packit 2638f0
=item $afm->FamilyName
Packit 2638f0
Packit 2638f0
Human-readable name for a group of fonts that are stylistic variants
Packit 2638f0
of a single design. All fonts that are members of such a group should
Packit 2638f0
have exactly the same C<FamilyName>. Example of a family name is
Packit 2638f0
"Times".
Packit 2638f0
Packit 2638f0
=item $afm->Weight
Packit 2638f0
Packit 2638f0
Human-readable name for the weight, or "boldness", attribute of a font.
Packit 2638f0
Examples are C<Roman>, C<Bold>, C<Light>.
Packit 2638f0
Packit 2638f0
=item $afm->ItalicAngle
Packit 2638f0
Packit 2638f0
Angle in degrees counterclockwise from the vertical of the dominant
Packit 2638f0
vertical strokes of the font.
Packit 2638f0
Packit 2638f0
=item $afm->IsFixedPitch
Packit 2638f0
Packit 2638f0
If C<true>, the font is a fixed-pitch
Packit 2638f0
(monospaced) font.
Packit 2638f0
Packit 2638f0
=item $afm->FontBBox
Packit 2638f0
Packit 2638f0
A string of four numbers giving the lower-left x, lower-left y,
Packit 2638f0
upper-right x, and upper-right y of the font bounding box. The font
Packit 2638f0
bounding box is the smallest rectangle enclosing the shape that would
Packit 2638f0
result if all the characters of the font were placed with their
Packit 2638f0
origins coincident, and then painted.
Packit 2638f0
Packit 2638f0
=item $afm->UnderlinePosition
Packit 2638f0
Packit 2638f0
Recommended distance from the baseline for positioning underline
Packit 2638f0
strokes. This number is the y coordinate of the center of the stroke.
Packit 2638f0
Packit 2638f0
=item $afm->UnderlineThickness
Packit 2638f0
Packit 2638f0
Recommended stroke width for underlining.
Packit 2638f0
Packit 2638f0
=item $afm->Version
Packit 2638f0
Packit 2638f0
Version number of the font.
Packit 2638f0
Packit 2638f0
=item $afm->Notice
Packit 2638f0
Packit 2638f0
Trademark or copyright notice, if applicable.
Packit 2638f0
Packit 2638f0
=item $afm->Comment
Packit 2638f0
Packit 2638f0
Comments found in the AFM file.
Packit 2638f0
Packit 2638f0
=item $afm->EncodingScheme
Packit 2638f0
Packit 2638f0
The name of the standard encoding scheme for the font. Most Adobe
Packit 2638f0
fonts use the C<AdobeStandardEncoding>. Special fonts might state
Packit 2638f0
C<FontSpecific>.
Packit 2638f0
Packit 2638f0
=item $afm->CapHeight
Packit 2638f0
Packit 2638f0
Usually the y-value of the top of the capital H.
Packit 2638f0
Packit 2638f0
=item $afm->XHeight
Packit 2638f0
Packit 2638f0
Typically the y-value of the top of the lowercase x.
Packit 2638f0
Packit 2638f0
=item $afm->Ascender
Packit 2638f0
Packit 2638f0
Typically the y-value of the top of the lowercase d.
Packit 2638f0
Packit 2638f0
=item $afm->Descender
Packit 2638f0
Packit 2638f0
Typically the y-value of the bottom of the lowercase p.
Packit 2638f0
Packit 2638f0
=item $afm->Wx
Packit 2638f0
Packit 2638f0
Returns a hash table that maps from glyph names to the width of that glyph.
Packit 2638f0
Packit 2638f0
=item $afm->BBox
Packit 2638f0
Packit 2638f0
Returns a hash table that maps from glyph names to bounding box information.
Packit 2638f0
The bounding box consist of four numbers: llx, lly, urx, ury.
Packit 2638f0
Packit 2638f0
=item $afm->dump
Packit 2638f0
Packit 2638f0
Dumps the content of the Font::AFM object to STDOUT.  Might sometimes
Packit 2638f0
be useful for debugging.
Packit 2638f0
Packit 2638f0
=back
Packit 2638f0
Packit 2638f0
Packit 2638f0
The AFM specification can be found at:
Packit 2638f0
Packit 2638f0
   http://partners.adobe.com/asn/developer/pdfs/tn/5004.AFM_Spec.pdf
Packit 2638f0
Packit 2638f0
Packit 2638f0
=head1 ENVIRONMENT
Packit 2638f0
Packit 2638f0
=over 10
Packit 2638f0
Packit 2638f0
=item METRICS
Packit 2638f0
Packit 2638f0
Contains the path to search for AFM-files.  Format is as for the PATH
Packit 2638f0
environment variable. The default path built into this library is:
Packit 2638f0
Packit 2638f0
 /usr/lib/afm:/usr/local/lib/afm:/usr/openwin/lib/fonts/afm/:.
Packit 2638f0
Packit 2638f0
=back
Packit 2638f0
Packit 2638f0
Packit 2638f0
=head1 BUGS
Packit 2638f0
Packit 2638f0
Kerning data and composite character data are not yet parsed.
Packit 2638f0
Ligature data is not parsed.
Packit 2638f0
Packit 2638f0
Packit 2638f0
=head1 COPYRIGHT
Packit 2638f0
Packit 2638f0
Copyright 1995-1998 Gisle Aas. All rights reserved.
Packit 2638f0
Packit 2638f0
This program is free software; you can redistribute it and/or modify
Packit 2638f0
it under the same terms as Perl itself.
Packit 2638f0
Packit 2638f0
=cut
Packit 2638f0
Packit 2638f0
#-------perl resumes here--------------------------------------------
Packit 2638f0
Packit 2638f0
use Carp;
Packit 2638f0
use strict;
Packit 2638f0
use vars qw($VERSION @ISOLatin1Encoding);
Packit 2638f0
Packit 2638f0
$VERSION = "1.20";
Packit 2638f0
Packit 2638f0
Packit 2638f0
# The metrics_path is used to locate metrics files
Packit 2638f0
#
Packit 2638f0
my $metrics_path = $ENV{METRICS} ||
Packit 2638f0
    "/usr/lib/afm:/usr/local/lib/afm:/usr/openwin/lib/fonts/afm/:.";
Packit 2638f0
my @metrics_path = split(/:/, $metrics_path);
Packit 2638f0
foreach (@metrics_path) { s,/$,, }    # reove trailing slashes
Packit 2638f0
Packit 2638f0
@ISOLatin1Encoding = qw(
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef space
Packit 2638f0
 exclam quotedbl numbersign dollar percent ampersand quoteright
Packit 2638f0
 parenleft parenright asterisk plus comma minus period slash zero one
Packit 2638f0
 two three four five six seven eight nine colon semicolon less equal
Packit 2638f0
 greater question at A B C D E F G H I J K L M N O P Q R S
Packit 2638f0
 T U V W X Y Z bracketleft backslash bracketright asciicircum
Packit 2638f0
 underscore quoteleft a b c d e f g h i j k l m n o p q r s
Packit 2638f0
 t u v w x y z braceleft bar braceright asciitilde .notdef .notdef
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef
Packit 2638f0
 .notdef .notdef .notdef .notdef .notdef .notdef .notdef dotlessi grave
Packit 2638f0
 acute circumflex tilde macron breve dotaccent dieresis .notdef ring
Packit 2638f0
 cedilla .notdef hungarumlaut ogonek caron space exclamdown cent
Packit 2638f0
 sterling currency yen brokenbar section dieresis copyright ordfeminine
Packit 2638f0
 guillemotleft logicalnot hyphen registered macron degree plusminus
Packit 2638f0
 twosuperior threesuperior acute mu paragraph periodcentered cedilla
Packit 2638f0
 onesuperior ordmasculine guillemotright onequarter onehalf threequarters
Packit 2638f0
 questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE
Packit 2638f0
 Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex
Packit 2638f0
 Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis
Packit 2638f0
 multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn
Packit 2638f0
 germandbls agrave aacute acircumflex atilde adieresis aring ae
Packit 2638f0
 ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex
Packit 2638f0
 idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide
Packit 2638f0
 oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis
Packit 2638f0
);
Packit 2638f0
Packit 2638f0
Packit 2638f0
# Creates a new Font::AFM object.  Pass it the name of the font as parameter.
Packit 2638f0
# Synopisis:
Packit 2638f0
#
Packit 2638f0
#    $h = new Font::AFM "Helvetica";
Packit 2638f0
#
Packit 2638f0
Packit 2638f0
sub new
Packit 2638f0
{
Packit 2638f0
   my($class, $fontname) = @_;
Packit 2638f0
   my $file;
Packit 2638f0
   $fontname =~ s/\.afm$//;
Packit 2638f0
   if ($^O eq 'VMS') {
Packit 2638f0
       $file = "sys\$ps_font_metrics:$fontname.afm";
Packit 2638f0
   } else {
Packit 2638f0
       $file = "$fontname.afm";
Packit 2638f0
       unless ($file =~ m,^/,) {
Packit 2638f0
	   # not absolute, search the metrics path for the file
Packit 2638f0
	   foreach (@metrics_path) {
Packit 2638f0
	       if (-f "$_/$file") {
Packit 2638f0
		   $file = "$_/$file";
Packit 2638f0
		   last;
Packit 2638f0
	       }
Packit 2638f0
	   }
Packit 2638f0
       }
Packit 2638f0
   }
Packit 2638f0
   open(AFM, $file) or croak "Can't find the AFM file for $fontname";
Packit 2638f0
   my $self = bless { }, $class;
Packit 2638f0
   local($/, $_) = ("\n", undef);  # ensure correct $INPUT_RECORD_SEPARATOR
Packit 2638f0
   while (<AFM>) {
Packit 2638f0
       next if /^StartKernData/ .. /^EndKernData/;  # kern data not parsed yet
Packit 2638f0
       next if /^StartComposites/ .. /^EndComposites/; # same for composites
Packit 2638f0
       if (/^StartCharMetrics/ .. /^EndCharMetrics/) {
Packit 2638f0
	   # only lines that start with "C" or "CH" are parsed
Packit 2638f0
	   next unless /^CH?\s/;
Packit 2638f0
	   my($name) = /\bN\s+(\.?\w+)\s*;/;
Packit 2638f0
	   my($wx)   = /\bWX\s+(\d+)\s*;/;
Packit 2638f0
	   my($bbox)    = /\bB\s+([^;]+);/;
Packit 2638f0
	   $bbox =~ s/\s+$//;
Packit 2638f0
	   # Should also parse lingature data (format: L successor lignature)
Packit 2638f0
	   $self->{'wx'}{$name} = $wx;
Packit 2638f0
	   $self->{'bbox'}{$name} = $bbox;
Packit 2638f0
	   next;
Packit 2638f0
       }
Packit 2638f0
       last if /^EndFontMetrics/;
Packit 2638f0
       if (/(^\w+)\s+(.*)/) {
Packit 2638f0
	   my($key,$val) = ($1, $2);
Packit 2638f0
	   $key = lc $key;
Packit 2638f0
	   if (defined $self->{$key}) {
Packit 2638f0
	       $self->{$key} = [ $self->{$key} ] unless ref $self->{$key};
Packit 2638f0
	       push(@{$self->{$key}}, $val);
Packit 2638f0
	   } else {
Packit 2638f0
	       $self->{$key} = $val;
Packit 2638f0
	   }
Packit 2638f0
       } else {
Packit 2638f0
	   print STDERR "Can't parse: $_";
Packit 2638f0
       }
Packit 2638f0
   }
Packit 2638f0
   close(AFM);
Packit 2638f0
   unless (exists $self->{wx}->{'.notdef'}) {
Packit 2638f0
       $self->{wx}->{'.notdef'} = 0;
Packit 2638f0
       $self->{bbox}{'.notdef'} = "0 0 0 0";
Packit 2638f0
   }
Packit 2638f0
   $self;
Packit 2638f0
}
Packit 2638f0
Packit 2638f0
# Returns an 256 element array that maps from characters to width
Packit 2638f0
sub latin1_wx_table
Packit 2638f0
{
Packit 2638f0
    my($self) = @_;
Packit 2638f0
    unless ($self->{'_wx_table'}) {
Packit 2638f0
	my @wx;
Packit 2638f0
	for (0..255) {
Packit 2638f0
	    my $name = $ISOLatin1Encoding[$_];
Packit 2638f0
	    if (exists $self->{wx}->{$name}) {
Packit 2638f0
		push(@wx, $self->{wx}->{$name})
Packit 2638f0
	    } else {
Packit 2638f0
		push(@wx, $self->{wx}->{'.notdef'});
Packit 2638f0
	    }
Packit 2638f0
	}
Packit 2638f0
	$self->{'_wx_table'} = \@wx;
Packit 2638f0
    }
Packit 2638f0
    wantarray ? @{ $self->{'_wx_table'} } : $self->{'_wx_table'};
Packit 2638f0
}
Packit 2638f0
Packit 2638f0
sub stringwidth
Packit 2638f0
{
Packit 2638f0
    my($self, $string, $pointsize) = @_;
Packit 2638f0
    return 0.0 unless defined $string;
Packit 2638f0
    return 0.0 unless length $string;
Packit 2638f0
Packit 2638f0
    my @wx = $self->latin1_wx_table;
Packit 2638f0
    my $width = 0.0;
Packit 2638f0
    for (unpack("C*", $string)) {
Packit 2638f0
	$width += $wx[$_];
Packit 2638f0
    }
Packit 2638f0
    if ($pointsize) {
Packit 2638f0
	$width *= $pointsize / 1000;
Packit 2638f0
    }
Packit 2638f0
    $width;
Packit 2638f0
}
Packit 2638f0
Packit 2638f0
sub FontName;
Packit 2638f0
sub FullName;
Packit 2638f0
sub FamilyName;
Packit 2638f0
sub Weight;
Packit 2638f0
sub ItalicAngle;
Packit 2638f0
sub IsFixedPitch;
Packit 2638f0
sub FontBBox;
Packit 2638f0
sub UnderlinePosition;
Packit 2638f0
sub UnderlineThickness;
Packit 2638f0
sub Version;
Packit 2638f0
sub Notice;
Packit 2638f0
sub Comment;
Packit 2638f0
sub EncodingScheme;
Packit 2638f0
sub CapHeight;
Packit 2638f0
sub XHeight;
Packit 2638f0
sub Ascender;
Packit 2638f0
sub Descender;
Packit 2638f0
sub Wx;
Packit 2638f0
sub BBox;
Packit 2638f0
Packit 2638f0
# We implement all the access functions within this simple autoload
Packit 2638f0
# function.
Packit 2638f0
Packit 2638f0
sub AUTOLOAD
Packit 2638f0
{
Packit 2638f0
    no strict 'vars';  # don't want to declare $AUTOLOAD
Packit 2638f0
Packit 2638f0
    #print "AUTOLOAD: $AUTOLOAD\n";
Packit 2638f0
    if ($AUTOLOAD =~ /::DESTROY$/) {
Packit 2638f0
	eval "sub $AUTOLOAD {}";
Packit 2638f0
	goto &$AUTOLOAD;
Packit 2638f0
    } else {
Packit 2638f0
	my $name = $AUTOLOAD;
Packit 2638f0
	$name =~ s/^.*:://;
Packit 2638f0
	croak "Attribute $name not defined for AFM object"
Packit 2638f0
	    unless defined $_[0]->{lc $name};
Packit 2638f0
	return $_[0]->{lc $name};
Packit 2638f0
    }
Packit 2638f0
}
Packit 2638f0
Packit 2638f0
Packit 2638f0
# Dumping might be useful for debugging
Packit 2638f0
Packit 2638f0
sub dump
Packit 2638f0
{
Packit 2638f0
    my($self) = @_;
Packit 2638f0
    my($key, $val);
Packit 2638f0
    foreach $key (sort keys %$self) {
Packit 2638f0
	if (ref $self->{$key}) {
Packit 2638f0
	    if (ref $self->{$key} eq "ARRAY") {
Packit 2638f0
		print "$key = [\n\t", join("\n\t", @{$self->{$key}}), "\n]\n";
Packit 2638f0
	    } elsif (ref $self->{$key} eq "HASH") {
Packit 2638f0
		print "$key = {\n";
Packit 2638f0
		my $key2;
Packit 2638f0
		foreach $key2 (sort keys %{$self->{$key}}) {
Packit 2638f0
		    print "\t$key2 => $self->{$key}{$key2},\n";
Packit 2638f0
		}
Packit 2638f0
		print "}\n";
Packit 2638f0
	    } else {
Packit 2638f0
		print "$key = $self->{$key}\n";
Packit 2638f0
	    }
Packit 2638f0
	} else {
Packit 2638f0
	    print "$key = $self->{$key}\n";
Packit 2638f0
	}
Packit 2638f0
    }
Packit 2638f0
}
Packit 2638f0
Packit 2638f0
1;