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   401 use strict;
  2         2  
  2         68  
14 2     2   10 use Carp;
  2         2  
  2         132  
15              
16 2     2   77 BEGIN { require 5.005; }
17              
18 2     2   13 use IO qw(File);
  2         3  
  2         8  
19 2     2   160 use File::Spec;
  2         2  
  2         31  
20              
21 2     2   7 use vars qw($VERSION);
  2         2  
  2         83  
22             $VERSION = "1.06";
23              
24 2     2   6 use constant FONTSCALE => 1000; # normal value for font design
  2         2  
  2         5852  
25              
26             sub new {
27 1     1 1 14 my $class = shift;
28 1         1 my $font = shift;
29 1         6 my (%atts) = (error => 'die',
30             verbose => 0, trace => 0,
31             @_);
32 1         3 my $self = { file => $font };
33 1         2 bless $self, $class;
34              
35 1 50       3 return $self unless defined $font;
36              
37 1         7 $self->{debug} = $atts{debug};
38 1   33     6 $self->{trace} = $self->{debug} || $atts{trace};
39 1   33     5 $self->{verbose} = $self->{trace} || $atts{verbose};
40              
41 1         2 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         6 };
46              
47 1         2 eval { $self->_loadafm };
  1         2  
48 1 50       4 if ( $@ ) {
49 0         0 $self->_die($@);
50 0         0 return undef;
51             }
52              
53 1         8 $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 210 my $self = shift;
62 219 100       397 $self->_getwidthdata() unless defined $self->{Wx};
63 219         276 $self->{Wx};
64             }
65              
66             sub EncodingVector {
67 219     219 1 178 my $self = shift;
68 219 50       367 $self->_getwidthdata() unless defined $self->{encodingvector};
69 219         235 $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 166 my $self = shift;
80 216 100       364 $self->_getkerndata() unless defined $self->{Kern};
81 216         232 $self->{Kern};
82             }
83              
84             sub _loadafm ($) {
85              
86 1     1   2 my ($self) = shift;
87              
88 1         1 my $data; # afm data
89              
90 1         2 my $fn = $self->{file};
91 1         13 my $fh = new IO::File; # font file
92 1         62 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         1 my $len = 0;
99 1         4 binmode($fh); # (Some?) Windows need this
100 1 50       4 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         15 $self->{origdataformat} = 'afm';
105 1 50       3 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         3 while ( $fh->sysread ($data, 32768, $len) > 0 ) {
125 1         15 $len = length ($data);
126             }
127 1         33 $fh->close;
128 1 50       14 print STDERR ("Read $len bytes from $fn\n") if $self->{trace};
129 1 50 33     7 $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         1091 $data =~ s/\s*\015\012?/\n/g;
135              
136 1 50 33     20 if ( $data !~ /StartFontMetrics/ || $data !~ /EndFontMetrics/ ) {
137 0         0 $self->_die("$fn: Not a recognizable AFM file\n");
138             }
139 1         3 $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         2 local ($_);
144 1         93 foreach ( split (/\n/, $self->{data}) ) {
145 598 100       815 next if /^StartKernData/ .. /^EndKernData/;
146 318 100       404 next if /^StartComposites/ .. /^EndComposites/;
147 260 100       418 next if /^StartCharMetrics/ .. /^EndCharMetrics/;
148 30 100       45 last if /^EndFontMetrics/;
149 29 100       83 if ( /^FontBBox\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s*$/ ) {
    50          
150 1         11 $self->{fontbbox} = [$1,$2,$3,$4];
151             }
152             elsif ( /(^\w+)\s+(.*)/ ) {
153 28         44 my ($key, $val) = ($1, $2);
154 28         25 $key = lc ($key);
155 28 100       34 if ( defined $self->{$key}) {
156 11 100       24 $self->{$key} = [ $self->{$key} ] unless ref $self->{$key};
157 11         10 push (@{$self->{$key}}, $val);
  11         26  
158             }
159             else {
160 17         29 $self->{$key} = $val;
161             }
162             }
163             }
164              
165 1         35 $self;
166             }
167              
168             sub _getwidthdata {
169             # This is adapted from Gisle Aas' Font-AFM 1.17
170 1     1   1 my $self = shift;
171 1         2 local ($_);
172 1         2 my %wx;
173 1         2 my $dontencode = 1;
174 1 50       5 unless ( defined $self->{encodingvector} ) {
175 1         1 $dontencode = 0;
176 1 50       7 if ( defined $self->{encodingscheme} ) {
177 1 50       4 if ( $self->{encodingscheme} eq "AdobeStandardEncoding" ) {
    0          
178 1         13 require PostScript::StandardEncoding;
179 1         8 $self->{encodingvector} =
180 1         3 [ @{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         2 my $enc = $self->{encodingvector};
198 1         2 my $nglyphs = 0;
199 1         2 my $nenc = 0;
200 1         118 foreach ( split (/\n/, $self->{data}) ) {
201 598 100       818 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       581 next unless /^CH?\s+(-?\d+|<[0-9a-f]+>)\s*;/i;
205 228         250 my $ix = $1;
206 228 50       369 $ix = hex($1) if $1 =~ /^<(.+)>$/;
207 228         600 my ($name) = /\bN\s+(\.?\w+(?:-\w+)?)\s*;/;
208 228         488 my ($wx) = /\bWX\s+(\d+)\s*;/;
209 228         390 $wx{$name} = $wx;
210 228         144 $nglyphs++;
211 228 50 33     352 $enc->[$ix] = $name, $nenc++ unless $dontencode || $ix < 0;
212 228         241 next;
213             }
214 368 100       493 last if /^EndFontMetrics/;
215             }
216 1 50       44 unless ( exists $wx{'.notdef'} ) {
217 1         4 $wx{'.notdef'} = 0;
218             }
219 1 50       8 print STDERR ($self->FileName, ": Number of glyphs = $nglyphs, ",
220             "encoded = $nenc\n") if $self->{verbose};
221 1         5 $self->{Wx} = \%wx;
222 1         5 $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         10 local ($_);
251 1         2 my $k = 0;
252 1         1 my %kern;
253 1         103 foreach ( split (/\n/, $self->{data}) ) {
254 598 100       766 if ( /^StartKern(Data|Pairs)/ .. /^EndKern(Data|Pairs)/ ) {
255 274 100       643 next unless /^KPX\s+(\S+)\s+(\S+)\s+(-?\d+)/;
256 271         658 $kern{$1,$2} = $3;
257 271         188 $k++;
258             }
259 595 100       832 last if /^EndFontMetrics/;
260             }
261 1 50       33 ${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         3 $self->{Kern} = \%kern;
265 1         4 $self;
266             }
267              
268             sub setEncoding {
269 1     1 1 3 my ($self, $enc) = @_;
270 1 50 33     14 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         15 delete $self->{_charcache};
290 1         2 $self;
291             }
292              
293             sub stringwidth {
294 3     3 1 125 my ($self, $string, $pt) = @_;
295              
296 3         10 my $wx = $self->CharWidthData;
297 3         8 my $ev = $self->EncodingVector;
298 3 50       3 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  3         12  
299 0         0 $self->_die($self->FileName . ": Missing Encoding\n");
300             }
301 3         4 my $wd = 0;
302 3         15 foreach ( unpack ("C*", $string) ) {
303 3   50     12 $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 3399 my ($self, $string, $pt) = @_;
316 195         240 my $wx = $self->CharWidthData;
317 195         263 my $ev = $self->EncodingVector;
318 195 50       141 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  195         337  
319 0         0 croak ($self->FileName . ": Missing Encoding\n");
320             }
321 195         246 my $kr = $self->KernData;
322 195         146 my $wd = 0;
323 195         132 my $prev;
324 195         354 foreach ( unpack ("C*", $string) ) {
325 1046   50     1584 my $this = $ev->[$_] || '.notdef';
326 1046         957 $wd += $wx->{$this};
327 1046 100       1356 if ( defined $prev ) {
328 851         958 my $kw = $kr->{$prev,$this};
329 851 100       1142 $wd += $kw if defined $kw;
330             }
331 1046         1023 $prev = $this;
332             }
333 195 50       354 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         424 $wd;
339             }
340              
341             sub kstring {
342 22     22 1 675 my ($self, $string, $ext) = @_;
343 22 0       45 return (wantarray ? () : []) unless length ($string);
    50          
344              
345 22         43 my $wx = $self->CharWidthData;
346 22         45 my $ev = $self->EncodingVector;
347 22 50       20 if ( scalar(@{$self->{encodingvector}}) <= 0 ) {
  22         43  
348 0         0 croak ($self->FileName . ": Missing Encoding\n");
349             }
350 22         85 my $kr = $self->KernData;
351 22 100       53 my $wd = (defined $ext ? $wx->{'space'}+$ext : 0);
352 22         28 my @res = ();
353 22         31 my $prev = '.undef';
354              
355 22         189 foreach ( split ('', $string) ) {
356              
357             # Check for flex space.
358 1327 100 100     3782 if ( defined $ext && $_ eq " " ) {
359             # If we have something, accumulate.
360 184 50       227 if ( @res ) {
361             # Add to displacement.
362 184 50       214 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         523 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  9         37  
368 184         285 $res[$#res] = "(".$res[$#res].")";
369 184         249 push (@res, $wd);
370             }
371             }
372             else {
373             # First item, push.
374 0         0 push (@res, $wd);
375             }
376 184         164 $prev = 'space';
377 184         190 next;
378             }
379              
380             # Get the glyph name and kern value.
381 1143   50     1957 my $this = $ev->[ord($_)] || '.undef';
382 1143   100     3380 my $kw = $kr->{$prev,$this} || 0;
383 1143         803 { local ($^W) = 0;
  1143         2011  
384 1143 50       2333 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     3419 if ( defined $ext && $prev eq 'space' ) {
    100          
389             # Accumulate displacement.
390 184         247 $res[$#res] += $kw;
391 184         295 push (@res, $_);
392             }
393             elsif ( $kw == 0 ) {
394 943 100       1002 if ( $prev eq '.undef' ) {
395             # New item.
396 22         36 push (@res, $_);
397             }
398             else {
399             # Accumulate text.
400 921         976 $res[$#res] .= $_;
401             }
402             }
403             else {
404             # Turn previous into string.
405 16         43 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  3         16  
406 16         27 $res[$#res] = "(".$res[$#res].")";
407             # Add kerning value and the new item.
408 16         30 push (@res, $kw, $_);
409             }
410 1143         1391 $prev = $this;
411             }
412              
413             # Turn the last item into string, if needed.
414 22 50 66     248 if ( !(defined $ext && $prev eq 'space') ) {
415 22         101 $res[$#res] =~ s/([()\\]|[^\040-\176])/sprintf("\\%03o",ord($1))/eg;
  7         18  
416 22         43 $res[$#res] = "(".$res[$#res].")";
417             }
418              
419             # Return.
420 22 50       175 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   17 no strict 'vars';
  2         2  
  2         393  
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         1 my $name = $AUTOLOAD;
453 1         8 $name =~ s/^.*:://;
454 1         8 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__