File Coverage

lib/PostScript/FontMetrics.pm
Criterion Covered Total %
statement 205 283 72.4
branch 77 144 53.4
condition 20 38 52.6
subroutine 20 27 74.0
pod 12 12 100.0
total 334 504 66.2


line stmt bran cond sub pod time code
1             # RCS Status : $Id: FontMetrics.pm,v 1.25 2005-06-10 22:56:21+02 jv Exp $
2             # Author : Johan Vromans
3             # Created On : December 1998
4             # Last Modified By: Johan Vromans
5             # Last Modified On: Fri Jun 10 19:00:43 2005
6             # Update Count : 479
7             # Status : Released
8              
9             ################ Module Preamble ################
10              
11             package PostScript::FontMetrics;
12              
13 2     2   554 use strict;
  2         5  
  2         83  
14 2     2   11 use Carp;
  2         4  
  2         186  
15              
16 2     2   118 BEGIN { require 5.005; }
17              
18 2     2   24 use IO qw(File);
  2         2  
  2         15  
19 2     2   360 use File::Spec;
  2         5  
  2         48  
20              
21 2     2   8 use vars qw($VERSION);
  2         4  
  2         104  
22             $VERSION = "1.06";
23              
24 2     2   11 use constant FONTSCALE => 1000; # normal value for font design
  2         3  
  2         8896  
25              
26             sub new {
27 1     1 1 17 my $class = shift;
28 1         2 my $font = shift;
29 1         9 my (%atts) = (error => 'die',
30             verbose => 0, trace => 0,
31             @_);
32 1         3 my $self = { file => $font };
33 1         4 bless $self, $class;
34              
35 1 50       4 return $self unless defined $font;
36              
37 1         9 $self->{debug} = $atts{debug};
38 1   33     7 $self->{trace} = $self->{debug} || $atts{trace};
39 1   33     6 $self->{verbose} = $self->{trace} || $atts{verbose};
40              
41 1         3 my $error = lc($atts{error});
42             $self->{die} = sub {
43 0 0   0   0 die(@_) if $error eq "die";
44 0 0       0 warn(@_) if $error eq "warn";
45 1         7 };
46              
47 1         3 eval { $self->_loadafm };
  1         6  
48 1 50       5 if ( $@ ) {
49 0         0 $self->_die($@);
50 0         0 return undef;
51             }
52              
53 1         7 $self;
54             }
55              
56 0     0 1 0 sub FileName { my $self = shift; $self->{file}; }
  0         0  
57              
58 0     0 1 0 sub MetricsData { my $self = shift; $self->{data}; }
  0         0  
59              
60             sub CharWidthData {
61 219     219 1 282 my $self = shift;
62 219 100       511 $self->_getwidthdata() unless defined $self->{Wx};
63 219         397 $self->{Wx};
64             }
65              
66             sub EncodingVector {
67 219     219 1 255 my $self = shift;
68 219 50       472 $self->_getwidthdata() unless defined $self->{encodingvector};
69 219         360 $self->{encodingvector};
70             }
71              
72             sub CharBBoxData {
73 0     0 1 0 my $self = shift;
74 0 0       0 $self->_getbboxdata() unless defined $self->{BBox};
75 0         0 $self->{BBox};
76             }
77              
78             sub KernData {
79 216     216 1 266 my $self = shift;
80 216 100       437 $self->_getkerndata() unless defined $self->{Kern};
81 216         387 $self->{Kern};
82             }
83              
84             sub _loadafm ($) {
85              
86 1     1   2 my ($self) = shift;
87              
88 1         2 my $data; # afm data
89              
90 1         2 my $fn = $self->{file};
91 1         14 my $fh = new IO::File; # font file
92 1         58 my $sz = -s $fn; # file size
93              
94 1 50       6 $fh->open ($fn) || $self->_die("$fn: $!\n");
95 1 50       50 print STDERR ("$fn: Loading AFM file\n") if $self->{verbose};
96              
97             # Read in the afm data.
98 1         2 my $len = 0;
99 1         3 binmode($fh); # (Some?) Windows need this
100 1 50       5 unless ( ($len = $fh->sysread ($data, 4, 0)) == 4 ) {
101 0         0 $self->_die("$fn: Expecting $sz bytes, got $len bytes\n");
102             }
103              
104 1         18 $self->{origdataformat} = 'afm';
105 1 50       4 if ( $data eq "\0\1\0\0" ) {
106 0         0 $fh->close;
107             # For the time being, Font::TTF is optional. Be careful.
108 0         0 eval {
109 0         0 require PostScript::Font::TTtoType42;
110             };
111 0 0       0 if ( $@ ) {
112 0         0 $self->_die("$fn: Cannot convert True Type font\n");
113             }
114 0         0 my $wrapper =
115             new PostScript::Font::TTtoType42:: ($fn,
116             verbose => $self->{verbose},
117             trace => $self->{trace},
118             debug => $self->{debug});
119 0         0 $data = ${$wrapper->afm_as_string};
  0         0  
120 0         0 $self->{t42wrapper} = $wrapper;
121 0         0 $self->{origdataformat} = 'ttf';
122             }
123             else {
124 1         5 while ( $fh->sysread ($data, 32768, $len) > 0 ) {
125 1         29 $len = length ($data);
126             }
127 1         92 $fh->close;
128 1 50       23 print STDERR ("Read $len bytes from $fn\n") if $self->{trace};
129 1 50 33     9 $self->_die("$fn: Expecting $sz bytes, got $len bytes\n")
130             unless $sz == -1 || $sz == $len;
131             }
132              
133             # Normalise line endings. Get rid of trailing space as well.
134 1         1810 $data =~ s/\s*\015\012?/\n/g;
135              
136 1 50 33     24 if ( $data !~ /StartFontMetrics/ || $data !~ /EndFontMetrics/ ) {
137 0         0 $self->_die("$fn: Not a recognizable AFM file\n");
138             }
139 1         4 $self->{data} = $data;
140              
141             # Initially, we only load the "global" info from the AFM data.
142             # Other parts are parsed when required.
143 1         3 local ($_);
144 1         163 foreach ( split (/\n/, $self->{data}) ) {
145 598 100       1080 next if /^StartKernData/ .. /^EndKernData/;
146 318 100       561 next if /^StartComposites/ .. /^EndComposites/;
147 260 100       515 next if /^StartCharMetrics/ .. /^EndCharMetrics/;
148 30 100       116 last if /^EndFontMetrics/;
149 29 100       143 if ( /^FontBBox\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s*$/ ) {
    50          
150 1         8 $self->{fontbbox} = [$1,$2,$3,$4];
151             }
152             elsif ( /(^\w+)\s+(.*)/ ) {
153 28         68 my ($key, $val) = ($1, $2);
154 28         48 $key = lc ($key);
155 28 100       58 if ( defined $self->{$key}) {
156 11 100       42 $self->{$key} = [ $self->{$key} ] unless ref $self->{$key};
157 11         13 push (@{$self->{$key}}, $val);
  11         40  
158             }
159             else {
160 17         58 $self->{$key} = $val;
161             }
162             }
163             }
164              
165 1         44 $self;
166             }
167              
168             sub _getwidthdata {
169             # This is adapted from Gisle Aas' Font-AFM 1.17
170 1     1   3 my $self = shift;
171 1         2 local ($_);
172 1         2 my %wx;
173 1         2 my $dontencode = 1;
174 1 50       6 unless ( defined $self->{encodingvector} ) {
175 1         2 $dontencode = 0;
176 1 50       7 if ( defined $self->{encodingscheme} ) {
177 1 50       4 if ( $self->{encodingscheme} eq "AdobeStandardEncoding" ) {
    0          
178 1         12 require PostScript::StandardEncoding;
179 1         10 $self->{encodingvector} =
180 1         2 [ @{PostScript::StandardEncoding->array} ];
181 1         3 $dontencode = 1;
182             }
183             elsif ( $self->{encodingscheme} eq "ISOLatin1Encoding" ) {
184 0         0 require PostScript::ISOLatin1Encoding;
185 0         0 $self->{encodingvector} =
186 0         0 [ @{PostScript::ISOLatin1Encoding->array} ];
187 0         0 $dontencode = 1;
188             }
189             else {
190 0         0 $self->{encodingvector} = [];
191             }
192             }
193             else {
194 0         0 $self->{encodingvector} = [];
195             }
196             }
197 1         4 my $enc = $self->{encodingvector};
198 1         2 my $nglyphs = 0;
199 1         2 my $nenc = 0;
200 1         196 foreach ( split (/\n/, $self->{data}) ) {
201 598 100       1285 if ( /^StartCharMetrics/ .. /^EndCharMetrics/ ) {
202             # Only lines that start with "C" or "CH" are parsed.
203             # C nn, C -1 or C (two hexits).
204 230 100       919 next unless /^CH?\s+(-?\d+|<[0-9a-f]+>)\s*;/i;
205 228         367 my $ix = $1;
206 228 50       545 $ix = hex($1) if $1 =~ /^<(.+)>$/;
207 228         941 my ($name) = /\bN\s+(\.?\w+(?:-\w+)?)\s*;/;
208 228         730 my ($wx) = /\bWX\s+(\d+)\s*;/;
209 228         648 $wx{$name} = $wx;
210 228         262 $nglyphs++;
211 228 50 33     517 $enc->[$ix] = $name, $nenc++ unless $dontencode || $ix < 0;
212 228         373 next;
213             }
214 368 100       701 last if /^EndFontMetrics/;
215             }
216 1 50       53 unless ( exists $wx{'.notdef'} ) {
217 1         4 $wx{'.notdef'} = 0;
218             }
219 1 50       7 print STDERR ($self->FileName, ": Number of glyphs = $nglyphs, ",
220             "encoded = $nenc\n") if $self->{verbose};
221 1         5 $self->{Wx} = \%wx;
222 1         4 $self;
223             }
224              
225             sub _getbboxdata {
226             # This is adapted from Gisle Aas' Font-AFM 1.17
227 0     0   0 my $self = shift;
228 0         0 local ($_);
229 0         0 my %bbox;
230 0         0 foreach ( split (/\n/, $self->{data}) ) {
231 0 0       0 if ( /^StartCharMetrics/ .. /^EndCharMetrics/ ) {
232             # Only lines that start with "C" or "CH" are parsed.
233 0 0       0 next unless /^CH?\s/;
234 0         0 my ($name) = /\bN\s+(\.?\w+)\s*;/;
235 0         0 my (@bbox) = /\bB\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s*;/;
236 0         0 $bbox{$name} = \@bbox;
237 0         0 next;
238             }
239 0 0       0 last if /^EndFontMetrics/;
240             }
241 0 0       0 unless ( exists $bbox{'.notdef'} ) {
242 0         0 $bbox{'.notdef'} = [0, 0, 0, 0];
243             }
244 0         0 $self->{BBox} = \%bbox;
245 0         0 $self;
246             }
247              
248             sub _getkerndata {
249 1     1   2 my $self = shift;
250 1         16 local ($_);
251 1         2 my $k = 0;
252 1         3 my %kern;
253 1         178 foreach ( split (/\n/, $self->{data}) ) {
254 598 100       1210 if ( /^StartKern(Data|Pairs)/ .. /^EndKern(Data|Pairs)/ ) {
255 274 100       935 next unless /^KPX\s+(\S+)\s+(\S+)\s+(-?\d+)/;
256 271         978 $kern{$1,$2} = $3;
257 271         328 $k++;
258             }
259 595 100       1215 last if /^EndFontMetrics/;
260             }
261 1 50       45 ${kern}{'.notdef','.notdef'} = 0 unless %kern;
262 1 50       6 print STDERR ($self->FileName, ": Number of kern pairs = $k\n")
263             if $self->{verbose};
264 1         2 $self->{Kern} = \%kern;
265 1         4 $self;
266             }
267              
268             sub setEncoding {
269 1     1 1 3 my ($self, $enc) = @_;
270 1 50 33     45 if ( ref($enc) && UNIVERSAL::isa($enc,'ARRAY') && scalar(@$enc) == 256 ) {
    0 33        
    0          
    0          
271             # Array ref is okay.
272             }
273             elsif ( $enc eq "StandardEncoding" ) {
274 0         0 require PostScript::StandardEncoding;
275 0         0 $enc = [ @{PostScript::StandardEncoding->array} ];
  0         0  
276             }
277             elsif ( $enc eq "ISOLatin1Encoding" ) {
278 0         0 require PostScript::ISOLatin1Encoding;
279 0         0 $enc = [ @{PostScript::ISOLatin1Encoding->array} ];
  0         0  
280             }
281             elsif ( $enc eq "ISOLatin9Encoding" ) {
282 0         0 require PostScript::ISOLatin9Encoding;
283 0         0 $enc = [ @{PostScript::ISOLatin9Encoding->array} ];
  0         0  
284             }
285             else {
286 0         0 croak ("Invalid encoding vector");
287             }
288 1         2 $self->{encodingvector} = $enc;
289 1         17 delete $self->{_charcache};
290 1         4 $self;
291             }
292              
293             sub stringwidth {
294 3     3 1 138 my ($self, $string, $pt) = @_;
295              
296 3         10 my $wx = $self->CharWidthData;
297 3         9 my $ev = $self->EncodingVector;
298 3 50       6 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  3         13  
299 0         0 $self->_die($self->FileName . ": Missing Encoding\n");
300             }
301 3         7 my $wd = 0;
302 3         14 foreach ( unpack ("C*", $string) ) {
303 3   50     16 $wd += $wx->{$ev->[$_]||'.undef'};
304             }
305              
306 3 50       10 if ( defined $pt ) {
307 0 0       0 carp ("Using a PointSize argument to stringwidth is deprecated")
308             if $^W;
309 0         0 $wd *= $pt / FONTSCALE;
310             }
311 3         12 $wd;
312             }
313              
314             sub kstringwidth {
315 195     195 1 4004 my ($self, $string, $pt) = @_;
316 195         331 my $wx = $self->CharWidthData;
317 195         380 my $ev = $self->EncodingVector;
318 195 50       229 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  195         470  
319 0         0 croak ($self->FileName . ": Missing Encoding\n");
320             }
321 195         358 my $kr = $self->KernData;
322 195         247 my $wd = 0;
323 195         238 my $prev;
324 195         471 foreach ( unpack ("C*", $string) ) {
325 1046   50     2178 my $this = $ev->[$_] || '.notdef';
326 1046         1594 $wd += $wx->{$this};
327 1046 100       1958 if ( defined $prev ) {
328 851         1412 my $kw = $kr->{$prev,$this};
329 851 100       1904 $wd += $kw if defined $kw;
330             }
331 1046         1840 $prev = $this;
332             }
333 195 50       448 if ( defined $pt ) {
334 0 0       0 carp ("Using a PointSize argument to kstringwidth is deprecated")
335             if $^W;
336 0         0 $wd *= $pt / FONTSCALE;
337             }
338 195         761 $wd;
339             }
340              
341             sub kstring {
342 22     22 1 2007 my ($self, $string, $ext) = @_;
343 22 0       56 return (wantarray ? () : []) unless length ($string);
    50          
344              
345 22         47 my $wx = $self->CharWidthData;
346 22         52 my $ev = $self->EncodingVector;
347 22 50       31 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  22         62  
348 0         0 croak ($self->FileName . ": Missing Encoding\n");
349             }
350 22         43 my $kr = $self->KernData;
351 22 100       63 my $wd = (defined $ext ? $wx->{'space'}+$ext : 0);
352 22         38 my @res = ();
353 22         32 my $prev = '.undef';
354              
355 22         227 foreach ( split ('', $string) ) {
356              
357             # Check for flex space.
358 1327 100 100     5016 if ( defined $ext && $_ eq " " ) {
359             # If we have something, accumulate.
360 184 50       323 if ( @res ) {
361             # Add to displacement.
362 184 50       279 if ( $prev eq 'space' ) {
363 0         0 $res[$#res] += $wd;
364             }
365             # Turn last item into string, and push the displacement.
366             else {
367 184         594 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  9         39  
368 184         379 $res[$#res] = "(".$res[$#res].")";
369 184         318 push (@res, $wd);
370             }
371             }
372             else {
373             # First item, push.
374 0         0 push (@res, $wd);
375             }
376 184         237 $prev = 'space';
377 184         265 next;
378             }
379              
380             # Get the glyph name and kern value.
381 1143   50     2717 my $this = $ev->[ord($_)] || '.undef';
382 1143   100     10032 my $kw = $kr->{$prev,$this} || 0;
383 1143         1241 { local ($^W) = 0;
  1143         2700  
384 1143 50       3122 print STDERR ("$prev $this $kw :$res[-3]:$res[-2]:$res[-1]:\n")
385             if $self->{debug};
386             }
387             # Nothing to kern?
388 1143 100 100     4470 if ( defined $ext && $prev eq 'space' ) {
    100          
389             # Accumulate displacement.
390 184         307 $res[$#res] += $kw;
391 184         376 push (@res, $_);
392             }
393             elsif ( $kw == 0 ) {
394 943 100       1470 if ( $prev eq '.undef' ) {
395             # New item.
396 22         45 push (@res, $_);
397             }
398             else {
399             # Accumulate text.
400 921         1588 $res[$#res] .= $_;
401             }
402             }
403             else {
404             # Turn previous into string.
405 16         58 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  3         18  
406 16         37 $res[$#res] = "(".$res[$#res].")";
407             # Add kerning value and the new item.
408 16         41 push (@res, $kw, $_);
409             }
410 1143         1951 $prev = $this;
411             }
412              
413             # Turn the last item into string, if needed.
414 22 50 66     190 if ( !(defined $ext && $prev eq 'space') ) {
415 22         163 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  7         26  
416 22         58 $res[$#res] = "(".$res[$#res].")";
417             }
418              
419             # Return.
420 22 50       114 wantarray ? @res : \@res;
421             }
422              
423             sub char {
424 0     0 1 0 my ($self, $glyph) = @_;
425              
426             # Return from cache if possible.
427 0         0 my $char = $self->{_charcache}->{$glyph};
428 0 0       0 return $char if defined $char;
429              
430             # Look it up.
431 0         0 $char = -1;
432 0         0 foreach ( @{$self->EncodingVector} ) {
  0         0  
433 0         0 $char++;
434 0 0       0 next unless $_ eq $glyph;
435 0         0 return $self->{_charcache}->{$glyph} = pack("C",$char);
436             }
437 0         0 undef;
438             }
439              
440             sub AUTOLOAD {
441             # This is adapted from Gisle Aas' Font-AFM 1.17
442 2     2   22 no strict 'vars';
  2         3  
  2         520  
443              
444 1 50   1   5 if ( $AUTOLOAD =~ /::DESTROY$/ ) {
445             # Prevent the eval from clobbering outer eval error status.
446 0         0 local ($@);
447             # Define a dummy destoyer, and jump tp it.
448 0         0 eval "sub $AUTOLOAD {}";
449 0         0 goto &$AUTOLOAD;
450             }
451             else {
452 1         2 my $name = $AUTOLOAD;
453 1         8 $name =~ s/^.*:://;
454 1         6 return $_[0]->{lc $name};
455             }
456             }
457              
458             sub _die {
459 0     0     my ($self, @msg) = @_;
460 0           $self->{die}->(@msg);
461             }
462              
463             1;
464              
465             __END__