File Coverage

lib/PostScript/Font.pm
Criterion Covered Total %
statement 121 291 41.5
branch 47 182 25.8
condition 4 12 33.3
subroutine 17 39 43.5
pod 18 18 100.0
total 207 542 38.1


line stmt bran cond sub pod time code
1             # RCS Status : $Id: Font.pm,v 1.23 2005-06-10 22:56:37+02 jv Exp jv $
2             # Author : Johan Vromans
3             # Created On : December 1998
4             # Last Modified By: Johan Vromans
5             # Last Modified On: Wed Dec 6 13:38:31 2006
6             # Update Count : 466
7             # Status : Released
8              
9             ################ Module Preamble ################
10              
11             package PostScript::Font;
12              
13 1     1   574 use strict;
  1         1  
  1         32  
14              
15 1     1   50 BEGIN { require 5.005; }
16              
17 1     1   4 use IO qw(File);
  1         2  
  1         5  
18 1     1   78 use File::Spec;
  1         1  
  1         17  
19 1     1   335 use PostScript::StandardEncoding;
  1         2  
  1         20  
20 1     1   354 use PostScript::ISOLatin1Encoding;
  1         2  
  1         33  
21              
22 1     1   7 use vars qw($VERSION);
  1         2  
  1         64  
23             $VERSION = "1.04";
24              
25             # If you have the t1disasm program, have $t1disasm point to it.
26             # This speeds up the glyph fetching.
27 1     1   5 use vars qw($t1disasm);
  1         2  
  1         4285  
28              
29             sub new {
30 1     1 1 17 my $class = shift;
31 1         2 my $font = shift;
32 1         41 my (%atts) = (error => 'die',
33             format => 'ascii',
34             verbose => 0, trace => 0, debug => 0,
35             @_);
36 1         5 my $self = { file => $font };
37 1         3 bless $self, $class;
38              
39 1 50       4 return $self unless defined $font;
40              
41 1         8 $self->{debug} = $atts{debug};
42 1   33     8 $self->{trace} = $self->{debug} || $atts{trace};
43 1   33     7 $self->{verbose} = $self->{trace} || $atts{verbose};
44              
45 1         4 my $error = lc($atts{error});
46             $self->{die} = sub {
47 0 0   0   0 die(@_) if $error eq "die";
48 0 0       0 warn(@_) if $error eq "warn";
49 1         48 };
50              
51 1 50       6 $atts{format} = "ascii" if lc($atts{format}) eq "pfa";
52 1 50       15 $atts{format} = "binary" if lc($atts{format}) eq "pfb";
53              
54 1 50       9 $t1disasm = $self->_getexec ("t1disasm") unless defined $t1disasm;
55              
56 1         2 eval {
57              
58 1         5 $self->_loadfont ();
59              
60             # Reformat if needed.
61 1         4 $self->{format} = "ascii";
62 1 50       9 if ( lc($atts{format}) eq "asm" ) {
    50          
63 0 0       0 print STDERR ($self->{file}, ": Converting to ASM format\n")
64             if $self->{verbose};
65 0         0 $self->{data} = $self->_pfa2asm;
66 0         0 $self->{format} = "asm";
67             }
68             elsif ( lc($atts{format}) eq "binary" ) {
69 0 0       0 print STDERR ($self->{file}, ": Converting to Binary format\n")
70             if $self->{verbose};
71 0         0 $self->{data} = $self->_pfa2pfb;
72 0         0 $self->{format} = "binary";
73             }
74              
75             };
76              
77 1 50       3 if ( $@ ) {
78 0         0 $self->_die($@);
79 0         0 return undef;
80             }
81              
82 1         8 $self;
83             }
84              
85 0     0 1 0 sub FileName { $_[0]->{file}; }
86 0     0 1 0 sub FontName { $_[0]->{name}; }
87 0     0 1 0 sub FamilyName { $_[0]->{family}; }
88 0     0 1 0 sub FontType { $_[0]->{type}; }
89 0     0 1 0 sub Version { $_[0]->{version}; }
90 0     0 1 0 sub ItalicAngle { $_[0]->{italic}; }
91 0     0 1 0 sub isFixedPitch{ $_[0]->{fixed}; }
92 0     0 1 0 sub Weight { $_[0]->{weight}; }
93 1     1 1 19 sub FontMatrix { $_[0]->{fontmatrix};}
94 0     0 1 0 sub FontBBox { $_[0]->{fontbbox}; }
95 0     0 1 0 sub DataFormat { $_[0]->{dataformat} }
96              
97             sub FontData {
98 1     1 1 16 my ($self, $format) = @_;
99 1 50       4 if ( defined $format ) {
100             # Not yet supported!
101 0         0 return ${$self->_cvtinternal($format)};
  0         0  
102             }
103 1 50       5 $self->{data} = $self->_cvtinternal unless exists $self->{data};
104 1         2 ${$self->{data}};
  1         613  
105             }
106              
107             sub FontGlyphs {
108 0     0 1 0 my $self = shift;
109 0 0       0 $self->{glyphs} = $self->_getglyphnames unless exists $self->{glyphs};
110 0         0 $self->{glyphs};
111             }
112              
113             sub Encoding {
114 0     0 1 0 my $self = shift;
115 0 0       0 $self->{encoding} = $self->_getencoding unless exists $self->{encoding};
116 0         0 $self->{encoding};
117             }
118              
119             sub EncodingVector {
120 0     0 1 0 my $self = shift;
121 0         0 my $enc = $self->{encoding};
122 0 0       0 return $enc if ref($enc) eq "ARRAY";
123             # Return private copy for the standard encodings.
124 0 0       0 if ( $enc eq "StandardEncoding" ) {
    0          
125 0         0 return scalar PostScript::StandardEncoding->array;
126             }
127             elsif ( $enc eq "ISOLatin1Encoding" ) {
128 0         0 return scalar PostScript::ISOLatin1Encoding->array;
129             }
130 0         0 undef;
131             }
132              
133             sub StandardEncoding {
134 0     0 1 0 return scalar PostScript::StandardEncoding->array;
135             }
136              
137             sub ISOLatin1Encoding {
138 1     1 1 146 return scalar PostScript::ISOLatin1Encoding->array;
139             }
140              
141             sub _loadfont ($) {
142              
143 1     1   3 my $self = shift;
144 1         2 my $data; # font data
145             my $type;
146              
147 1         2 my $fn = $self->{file};
148 1         7 my $fh = new IO::File; # font file
149 1         42 my $sz = -s $fn; # file size
150              
151 1 50       8 $fh->open ($fn) || $self->_die("$fn: $!\n");
152 1 50       45 print STDERR ("$fn: Loading font file\n") if $self->{verbose};
153              
154             # Read in the font data.
155 1         10 binmode($fh); # (Some?) Windows need this
156 1         2 my $len = 0;
157 1         10 while ( $fh->sysread ($data, 32768, $len) > 0 ) {
158 2         156 $len = length ($data);
159             }
160 1         18 $fh->close;
161 1 50       17 $self->_die("$fn: Expecting at least 4 bytes, got ",
162             length($data), " bytes\n")
163             unless length($data) >= 4;
164              
165 1         3 $self->{dataformat} = 'ps';
166 1 50 33     10 if ( substr($data,0,4) eq "\0\1\0\0" || substr($data, 0, 4) eq "OTTO" ) {
167             # For the time being, Font::TTF is optional. Be careful.
168 0         0 eval {
169 0         0 require PostScript::Font::TTtoType42;
170             };
171 0 0       0 if ( $@ ) {
172 0         0 $self->_die("$fn: Cannot convert True Type font\n");
173 0         0 return undef;
174             }
175 0         0 my $wrapper =
176             new PostScript::Font::TTtoType42:: ($fn,
177             verbose => $self->{verbose},
178             trace => $self->{trace},
179             debug => $self->{debug},
180             );
181 0         0 $type = "t";
182 0         0 $data = $wrapper->as_string;
183 0         0 $self->{t42wrapper} = $wrapper;
184 0         0 $self->{dataformat} = "type42";
185 0         0 $self->{encoding} = "StandardEncoding";
186             }
187             else {
188             # Make ref.
189 1         89 $data = \"$data"; #";
190             }
191              
192 1 50       11 print STDERR ("Read $len bytes from $fn\n") if $self->{trace};
193 1 50 33     8 $self->_die("$fn: Expecting $sz bytes, got $len bytes\n")
194             if $sz > 0 && $sz != $len;
195              
196             # Convert .pfb encoded font data.
197 1 50       7 if ( $$data =~ /^\200[\001-\003]/ ) {
    0          
198 1 50       4 print STDERR ("$fn: Converting to ASCII format\n") if $self->{verbose};
199 1         6 $data = $self->_pfb2pfa ($data);
200 1         4 $self->{dataformat} = 'pfa';
201             }
202             # Otherwise, must be straight PostScript.
203             elsif ( $$data !~ /^%!/ ) {
204 0         0 $self->_die("$fn: Not a recognizable font file\n");
205 0         0 return undef;
206             }
207              
208             # Normalise line endings.
209 1         240 $$data =~ s/\015\012?/\n/g;
210              
211 1         4 $self->{data} = $data;
212 1 50       4 $self->{type} = $type if defined $type;
213              
214 1 50       14 if ( $$data =~ /^%!FontType(\d+)\n\/(\S+)\n/ ) {
    50          
    0          
215 0 0       0 $self->{type} = $1 unless defined $self->{type};
216 0         0 $self->{name} = $2;
217             }
218             elsif ( $$data =~ /\/FontName\s*\/(\S+)/ ) {
219 1         6 $self->{name} = $1;
220             }
221             elsif ( $$data =~ /\/FontName\s*\(([^\051]+)\)/ ) {
222 0         0 $self->{name} = $1;
223             }
224 1 50       42 if ( $$data =~ /\/FamilyName\s*\/(\S+)/ ) {
    50          
225 0         0 $self->{family} = $1;
226             }
227             elsif ( $$data =~ /\/FamilyName\s*\(([^\051]+)\)/ ) {
228 1         4 $self->{family} = $1;
229             }
230 1 50       6 unless ( defined $self->{type} ) {
231 1 50       10 $self->{type} = $1 if $$data =~ /\/FontType\s+(\d+)/;
232             }
233 1 50       10 $self->{version} = $1 if $$data =~ /\/version\s*\(([^\051]+)\)/;
234 1 50       8 $self->{italic} = $1 if $$data =~ /\/ItalicAngle\s+([-+]?\d+)/;
235 1 50       11 $self->{fixed} = $1 eq "true"
236             if $$data =~ /\/isFixedPitch\s+(true|false)/;
237 1 50       51 if ( $$data =~ /\/Weight\s*\/(\S+)/ ) {
    50          
238 0         0 $self->{weight} = $1;
239             }
240             elsif ( $$data =~ /\/Weight\s*\(([^\051]+)\)/ ) {
241 1         5 $self->{weight} = $1;
242             }
243 1 50       11 if ( $$data =~ /\/FontMatrix\s*\[\s*(\d+(?:\.\d*)?)\s+(\d+(?:\.\d*)?)\s+(\d+(?:\.\d*)?)\s+(\d+(?:\.\d*)?)\s+(\d+(?:\.\d*)?)\s+(\d+(?:\.\d*)?)\s*\]/ ) {
244 1         9 $self->{fontmatrix} = [$1,$2,$3,$4,$5,$6];
245             }
246 1 50       11 if ( $$data =~ /\/FontBBox\s*\{\s*(-?\d+(?:\.\d*)?)\s+(-?\d+(?:\.\d*)?)\s+(-?\d+(?:\.\d*)?)\s+(-?\d+(?:\.\d*)?)\s*\}/ ) {
247 1         11 $self->{fontbbox} = [$1,$2,$3,$4];
248             }
249 1         6 $self;
250             }
251              
252             sub _qtfn ($) {
253 0     0   0 my $f = shift;
254 0         0 $f =~ s/([\\'])/'\\$1'/g;
255 0         0 "'".$f."'";
256             }
257              
258             sub _cvtinternal {
259 0     0   0 my ($self, $format) = @_;
260 0 0       0 return $self->{data} if $format eq $self->{format};
261 0         0 undef;
262             }
263              
264             sub _pfb2pfa ($;$) {
265 1     1   3 my ($self, $data) = @_; # NOTE: data is a ref!
266 1         12 my $newdata = ""; # NOTE: will return a ref
267              
268 1 50       10 $data = $self->{data} unless defined $data;
269              
270             # Structure of .pfb font data:
271             #
272             # ( ASCII-segment | Binary-segment )+ EOF-indicator
273             #
274             # ASCII-segment: \200 \001 length data
275             # Binary-sement: \200 \002 length data
276             # EOF-indicator: \200 \003
277             #
278             # length is a 4-byte little endian 'long'.
279             # data are length bytes of data.
280              
281 1         2 my $bin = ""; # accumulated unprocessed binary segments
282             my $addbin = sub { # binary segment processor
283 1 50   1   4 print STDERR ("Processing binary segment, ",
284             length($bin), " bytes\n") if $self->{trace};
285 1         1709 ($bin = uc (unpack ("H*", $bin))) =~ s/(.{64})/$1\n/g;
286 1         123 $newdata .= $bin;
287 1 50       9 $newdata .= "\n" unless $newdata =~ /\n$/;
288 1         3 $bin = "";
289 1         7 };
290              
291 1         4 while ( length($$data) > 0 ) {
292 4         5 my ($type, $info, $seg);
293              
294 4 100       15 last if $$data =~ /^\200\003/; # EOF indicator
295              
296             # Get font segment.
297 3 50       21 $self->_die($self->{file}, ": Invalid font segment format\n")
298             unless ($type, $info) = $$data =~ /^\200([\001-\002])(....)/s;
299              
300 3         9 my $len = unpack ("V", $info);
301             # Can't use next statement since $len may be > 32766.
302             # ($seg, $$data) = $$data =~ /^......(.{$len})(.*)/s;
303 3         45 $seg = substr ($$data, 6, $len);
304 3         16 $$data = substr ($$data, $len+6);
305              
306 3 100       8 if ( ord($type) == 1 ) { # ASCII segment
307 2 100       9 $addbin->() if $bin ne "";
308 2 50       13 print STDERR ($self->{file}, ": ASCII segment, $len bytes\n")
309             if $self->{trace};
310 2         13 $newdata .= $seg;
311             }
312             else { # ord($type) == 2 # Binary segment
313 1 50       5 print STDERR ($self->{file}, ": Binary segment, $len bytes\n")
314             if $self->{trace};
315 1         50 $bin .= $seg;
316             }
317             }
318 1 50       5 $addbin->() if $bin ne "";
319 1         8 return \$newdata;
320             }
321              
322             sub _pfa2pfb ($;$) {
323 0     0   0 my ($self, $data) = @_; # NOTE: data is a ref!
324              
325 0 0       0 $data = $self->{data} unless defined $data;
326              
327 0 0       0 return \do{"\200\001".pack("V",length($$data)).$$data."\200\003"}
  0         0  
328             unless $$data =~ m{(^.*\beexec\s*\n+)
329             ([A-Fa-f0-9\n]+)
330             (\s*cleartomark.*$)}sx;
331              
332 0         0 my ($pre, $bin, $post) = ($1, $2, $3);
333 0         0 $bin =~ tr/A-Fa-f0-9//cd;
334 0         0 $bin = pack ("H*", $bin);
335 0         0 my $nulls;
336 0         0 ($bin, $nulls) = $bin =~ /(.*[^\0])(\0+)?$/s;
337 0 0       0 $nulls = defined $nulls ? length($nulls) : 0;
338 0         0 while ( $nulls > 0 ) {
339 0 0       0 $post = ("00" x ($nulls > 32 ? 32 : $nulls)) . "\n" . $post;
340 0         0 $nulls -= 32;
341             }
342              
343 0         0 return \do{"\200\001".pack("V",length($pre)).$pre.
  0         0  
344             "\200\002".pack("V",length($bin)).$bin.
345             "\200\001".pack("V",length($post)).$post.
346             "\200\003" };
347             }
348              
349             sub _pfa2asm ($;$) {
350 0     0   0 my ($self, $data) = @_; # NOTE: data is a ref!
351              
352 0 0       0 $data = $self->{data} unless defined $data;
353              
354 0 0       0 if ( $t1disasm ) {
355 0         0 my $fn = _qtfn($self->{file});
356             #### WARNING: This is Unix specific! ####
357 0         0 my $cmd = "$t1disasm $fn";
358 0 0       0 if ( $self->{type} eq 't' ) {
359 0         0 $self->_die($self->{file},
360             ": Logic error -- Cannot convert True Type font\n");
361             }
362              
363 0 0       0 print STDERR ("+ $cmd |\n") if $self->{trace};
364 0         0 my $fh = new IO::File ("$cmd |");
365 0         0 local ($/);
366 0         0 my $newdata = <$fh>;
367 0 0       0 $fh->close or warn ($cmd, ": return ". sprintf("%x", $?), "\n");
368 0         0 $newdata =~ s/\015\012?/\n/g;
369 0         0 return \$newdata;
370             }
371              
372 0 0       0 return $data
373             unless $$data =~ m{(^.*\beexec\s*\n+)
374             ([A-Fa-f0-9\n]+)
375             (\n\s*cleartomark.*$)}sx;
376              
377 0         0 my ($pre, $bin, $post) = ($1, $2, $3);
378 0         0 $bin =~ tr/A-Fa-f0-9//cd;
379 0         0 $bin = pack ("H*", $bin);
380 0         0 my $nulls;
381 0         0 ($bin, $nulls) = $bin =~ /(.*[^\0])(\0+)?$/s;
382 0 0       0 $nulls = defined $nulls ? length($nulls) : 0;
383 0         0 while ( $nulls > 0 ) {
384 0 0       0 $post = ("00" x ($nulls > 32 ? 32 : $nulls)) . "\n" . $post;
385 0         0 $nulls -= 32;
386             }
387              
388 0         0 my $newdata = "";
389              
390             # Conversion based on an C-program marked as follows:
391             # /* Written by Carsten Wiethoff 1989 */
392             # /* You may do what you want with this code,
393             # as long as this notice stays in it */
394              
395 0         0 my $input;
396             my $output;
397 0         0 my $ignore = 4;
398 0         0 my $buffer = 0xd971;
399              
400 0         0 while ( length($bin) > 0 ) {
401 0         0 ($input, $bin) = $bin =~ /^(.)(.*)$/s;
402 0         0 $input = ord ($input);
403 0         0 $output = $input ^ ($buffer >> 8);
404 0         0 $buffer = (($input + $buffer) * 0xce6d + 0x58bf) & 0xffff;
405 0 0       0 next if $ignore-- > 0;
406 0         0 $newdata .= pack ("C", $output);
407             }
408              
409             # End conversion.
410              
411             # Cleanup (for display only).
412 0         0 $newdata =~ s/ \-\| (.+?) (\|-?)\n/" -| <".unpack("H*",$1)."> $2\n"/ges;
  0         0  
413              
414             # Concatenate and return.
415 0         0 $newdata = $pre . $newdata . $post;
416 0         0 return \$newdata;
417             }
418              
419             sub _getglyphnames ($;$) {
420 0     0   0 my ($self, $data) = @_;
421 0         0 my @glyphs = ();
422              
423 0 0       0 print STDERR ($self->{file}, ": Getting glyph info\n") if $self->{verbose};
424              
425 0 0       0 $data = $self->{data} unless defined $data;
426              
427 0 0       0 if ( $self->{dataformat} eq "type42" ) {
428 0         0 my $g = $self->{t42wrapper}->glyphnames;
429 0 0       0 print STDERR ($self->{file}, ": Number of glyphs = ",
430             scalar(@$g), "\n")
431             if $self->{verbose};
432 0         0 return $g;
433             }
434              
435 0 0       0 if ( $self->{format} eq "binary" ) {
436 0         0 $data = $self->_pfb2pfa ($data);
437             }
438              
439 0 0       0 if ( $$data =~ m|/CharStrings\s.*\n((?s)(.*))| ) {
440 0         0 $data = $2;
441             }
442             else {
443 0         0 $data = $self->_pfa2asm ($data);
444 0 0       0 if ( $$data =~ m|/CharStrings\s.*\n((?s)(.*))| ) {
445 0         0 $data = $2;
446             }
447             else {
448 0         0 return undef;
449             }
450             }
451              
452             # while ( $data =~ m;((^\d*\s*/([.\w]+))|(\bend\b)).*\n;mg ) {
453 0         0 while ( $data =~ m;(/([.\w]+)\b|\bend\b);mg ) {
454 0 0       0 last if $1 eq "end";
455 0         0 push (@glyphs, $2);
456             }
457              
458 0 0       0 print STDERR ($self->{file}, ": Number of glyphs = ",
459             scalar(@glyphs), "\n")
460             if $self->{verbose};
461              
462 0         0 \@glyphs;
463             }
464              
465             sub _getencoding ($;$) {
466 0     0   0 my ($self, $data) = @_;
467 0         0 my @glyphs = ();
468              
469 0 0       0 print STDERR ($self->{file}, ": Getting encoding info\n")
470             if $self->{verbose};
471              
472 0 0       0 $data = $self->{data} unless defined $data;
473 0         0 $data = $$data; # deref
474 0         0 $data =~ s/\n\s*%.*$//mg; # strip comments
475              
476             # Name -> standard encoding.
477 0 0       0 return $1 if $data =~ m|/Encoding\s+(\S+)\s+def|;
478              
479             # Array -> explicit encoding.
480 0 0       0 if ( $data =~ m;/Encoding[\n\s]+\[([^\]]+)\][\n\s]+def;m ) {
481 0         0 my $enc = $1;
482 0         0 $enc =~ s|\s*/| |g;
483 0         0 $enc =~ s/^\s+//;
484 0         0 $enc =~ s/\s+$//;
485 0         0 $enc =~ s/\s+/ /g;
486 0 0       0 if ( $enc eq PostScript::StandardEncoding->string ) {
    0          
487 0         0 $enc = "StandardEncoding"
488             }
489             elsif ( $enc eq PostScript::ISOLatin1Encoding->string ) {
490 0         0 $enc = "ISOLatin1Encoding"
491             }
492             else {
493 0         0 $enc = [split (' ', $enc)];
494             }
495 0         0 return $enc;
496             }
497              
498             # Sparse array, probably custom encoding.
499             # if ( $data =~ m;/Encoding \d+ array\n(0 1 .*for\n)?((dup \d+\s*/\S+ put(\s*%.*)?\n)+); ) {
500 0 0       0 if ( $data =~ m;/Encoding \d+ array\n(0 1 .*for\n)?((dup \d+\s*/\S+ put(.*)\n)+); ) {
501 0         0 my $enc = $2;
502 0         0 my @enc = (".notdef") x 256;
503 0         0 while ( $enc =~ m;dup (\d+)\s*/(\S+) put;g ) {
504 0         0 $enc[$1] = $2;
505             }
506 0 0       0 if ( "@enc" eq PostScript::StandardEncoding->string ) {
    0          
507 0         0 $enc = "StandardEncoding"
508             }
509             elsif ( "@enc" eq PostScript::ISOLatin1Encoding->string ) {
510 0         0 $enc = "ISOLatin1Encoding"
511             }
512             else {
513 0         0 $enc = \@enc;
514             }
515 0         0 return $enc;
516             }
517              
518 0         0 undef;
519             }
520              
521             sub _getexec ($) {
522 1     1   3 my ($self, $exec) = @_;
523 1         24 foreach ( File::Spec->path ) {
524 7 50       259 if ( -x "$_/$exec" ) {
    50          
525 0 0       0 print STDERR ("Using $_/$exec\n") if $self->{verbose};
526 0         0 return "$_/$exec";
527             }
528             elsif ( -x "$_/$exec.exe" ) {
529 0 0       0 print STDERR ("Using $_/$exec.exe\n") if $self->{verbose};
530 0         0 return "$_/$exec.exe";
531             }
532             }
533             ''
534 1         4 }
535              
536             sub _die {
537 0     0   0 my ($self, @msg) = @_;
538 0         0 $self->{die}->(@msg);
539             }
540              
541             # PostScript::TTtoType42 is actually an Font::TTF::Font object.
542             # Font::TTF::Font uses cyclic structures, so we need this.
543             sub DESTROY {
544 1     1   654 my $self = shift;
545 1 50         $self->{t42wrapper}->DESTROY if $self->{t42wrapper};
546             }
547              
548             1;
549              
550             __END__