File Coverage

blib/lib/PDF/Builder/Resource/Font/Postscript.pm
Criterion Covered Total %
statement 21 318 6.6
branch 0 146 0.0
condition 0 96 0.0
subroutine 7 12 58.3
pod 1 5 20.0
total 29 577 5.0


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::Font::Postscript;
2              
3 1     1   1121 use base 'PDF::Builder::Resource::Font';
  1         2  
  1         97  
4              
5 1     1   5 use strict;
  1         2  
  1         16  
6 1     1   3 use warnings;
  1         2  
  1         57  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
10              
11 1     1   4 use Encode qw(:all);
  1         2  
  1         297  
12 1     1   6 use IO::File qw();
  1         2  
  1         31  
13              
14 1     1   4 use PDF::Builder::Util;
  1         2  
  1         158  
15 1     1   6 use PDF::Builder::Basic::PDF::Utils;
  1         2  
  1         4299  
16              
17             =head1 NAME
18              
19             PDF::Builder::Resource::Font::Postscript - Support routines for using PostScript (Type 1) fonts
20              
21             Inherits from L<PDF::Builder::Resource::Font>
22              
23             =head1 METHODS
24              
25             =head2 new
26              
27             PDF::Builder::Resource::Font::Postscript->new($pdf, $psfile, %opts)
28              
29             =over
30              
31             Create an object for a PostScript font. Handles ASCII (.pfa), binary (.pfb), and
32             T1 (.t1) font files, as well as ASCII (.afm) and binary (.pfm) metrics files.
33              
34             See L<PDF::Builder::Docs/PS Fonts> for additional information.
35              
36             Valid %opts are:
37              
38             =over
39              
40             =item encode
41              
42             Changes the encoding of the font from its default. Notice that the encoding
43             (I<not> the entire font's glyph list) is shown in a PDF object (record), listing
44             256 glyphs associated with this encoding (I<and> that are available in this
45             font).
46              
47             =item afmfile
48              
49             Specifies the location of the I<ASCII> font metrics file (.afm). It may be used
50             with either an ASCII (.pfa) or binary (.pfb) glyph file.
51              
52             C<afm_file> is still accepted as an (older) B<alternative> to C<afmfile>.
53              
54             =item pfmfile
55              
56             Specifies the location of the I<binary> font metrics file (.pfm). It may be used
57             with either an ASCII (.pfa) or binary (.pfb) glyph file.
58              
59             C<pfm_file> is still accepted as an (older) B<alternative> to C<pfmfile>.
60              
61             =item pdfname
62              
63             Changes the reference-name of the font from its default.
64             The reference-name is normally generated automatically and can be
65             retrieved via $pdfname=$font->name().
66              
67             =item dokern
68              
69             Enables kerning if data is available.
70              
71             C<kerning> is still accepted as an (older) B<alternative> to C<dokern>.
72              
73             =back
74              
75             =back
76              
77             =cut
78              
79             # TBD what is an xfm file? was xfm_file option ever supported?
80             # currently, xfmfile is a dummy stub, and not listed in POD
81              
82             sub new {
83 0     0 1   my ($class, $pdf, $psfile, %opts) = @_;
84             # copy dashed option names to preferred undashed names
85 0 0 0       if (defined $opts{'-encode'} && !defined $opts{'encode'}) { $opts{'encode'} = delete($opts{'-encode'}); }
  0            
86 0 0 0       if (defined $opts{'-afmfile'} && !defined $opts{'afmfile'}) { $opts{'afmfile'} = delete($opts{'-afmfile'}); }
  0            
87 0 0 0       if (defined $opts{'-afm_file'} && !defined $opts{'afm_file'}) { $opts{'afm_file'} = delete($opts{'-afm_file'}); }
  0            
88 0 0 0       if (defined $opts{'-pfmfile'} && !defined $opts{'pfmfile'}) { $opts{'pfmfile'} = delete($opts{'-pfmfile'}); }
  0            
89 0 0 0       if (defined $opts{'-pfm_file'} && !defined $opts{'pfm_file'}) { $opts{'pfm_file'} = delete($opts{'-pfm_file'}); }
  0            
90 0 0 0       if (defined $opts{'-xfmfile'} && !defined $opts{'xfmfile'}) { $opts{'xfmfile'} = delete($opts{'-xfmfile'}); }
  0            
91 0 0 0       if (defined $opts{'-xfm_file'} && !defined $opts{'xfm_file'}) { $opts{'xfm_file'} = delete($opts{'-xfm_file'}); }
  0            
92 0 0 0       if (defined $opts{'-pdfname'} && !defined $opts{'pdfname'}) { $opts{'pdfname'} = delete($opts{'-pdfname'}); }
  0            
93 0 0 0       if (defined $opts{'-nocomps'} && !defined $opts{'nocomps'}) { $opts{'nocomps'} = delete($opts{'-nocomps'}); }
  0            
94 0 0 0       if (defined $opts{'-dokern'} && !defined $opts{'dokern'}) { $opts{'dokern'} = delete($opts{'-dokern'}); }
  0            
95 0 0 0       if (defined $opts{'-kerning'} && !defined $opts{'kerning'}) { $opts{'kerning'} = delete($opts{'-kerning'}); }
  0            
96              
97 0           my ($self);
98             my ($data);
99              
100             # preferred option names
101 0 0 0       if (defined $opts{'kerning'} && !defined $opts{'dokern'}) { $opts{'dokern'} = delete($opts{'kerning'}); }
  0            
102 0 0 0       if (defined $opts{'afm_file'} && !defined $opts{'afmfile'}) { $opts{'afmfile'} = delete($opts{'afm_file'}); }
  0            
103 0 0 0       if (defined $opts{'pfm_file'} && !defined $opts{'pfmfile'}) { $opts{'pfmfile'} = delete($opts{'pfm_file'}); }
  0            
104 0 0 0       if (defined $opts{'xfm_file'} && !defined $opts{'xfmfile'}) { $opts{'xfmfile'} = delete($opts{'xfm_file'}); }
  0            
105              
106 0 0         if (defined $opts{'afmfile'}) {
    0          
    0          
107 0           $data = $class->readAFM($opts{'afmfile'});
108             } elsif (defined $opts{'pfmfile'}) {
109 0           $data = $class->readPFM($opts{'pfmfile'});
110             } elsif (defined $opts{'xfmfile'}) { # TBD what is it?
111 0           $data = $class->readXFM($opts{'xfmfile'}); # dummy stub
112             } else {
113 0           die "No proper font-metrics file specified for PostScript file '$psfile'.";
114             }
115              
116 0 0         $class = ref $class if ref $class;
117             # $self = $class->SUPER::new($pdf, $data->{'apiname'}.pdfkey().'~'.time());
118 0           $self = $class->SUPER::new($pdf, $data->{'apiname'}.'PST1f'.pdfkey());
119 0 0         $pdf->new_obj($self) unless $self->is_obj($pdf);
120 0           $self->{' data'} = $data;
121              
122 0 0         if ($opts{'pdfname'}) {
123 0           $self->name($opts{'pdfname'});
124             }
125              
126 0           $self->{'Subtype'} = PDFName("Type1");
127 0           $self->{'FontDescriptor'} = $self->descrByData();
128 0 0         if (-f $psfile) {
129             # $self->{'BaseFont'} = PDFName(pdfkey().'+'.$self->fontname().'~'.time());
130 0           $self->{'BaseFont'} = PDFName(pdfkey().'+'.$self->fontname());
131              
132 0           my ($l1,$l2,$l3, $stream) = $self->readPFAPFB($psfile);
133              
134 0           my $s = PDFDict();
135 0           $self->{'FontDescriptor'}->{'FontFile'} = $s;
136 0           $s->{'Length1'} = PDFNum($l1);
137 0           $s->{'Length2'} = PDFNum($l2);
138 0           $s->{'Length3'} = PDFNum($l3);
139 0           $s->{'Filter'} = PDFArray(PDFName("FlateDecode"));
140 0           $s->{' stream'} = $stream;
141 0 0         if (defined $pdf) {
142 0           $pdf->new_obj($s);
143             }
144             } else {
145 0           $self->{'BaseFont'} = PDFName($self->fontname());
146             }
147              
148 0 0 0       if (defined $opts{'encode'} && $opts{'encode'} =~ m/^utf/i) {
149 0           die "Invalid multibyte encoding for psfont: $opts{'encode'}\n";
150             # probably more encodings to check
151             }
152 0           $self->encodeByData($opts{'encode'}); # undef arg OK
153              
154 0 0         $self->{'-nocomps'} = 1 if $opts{'nocomps'};
155 0 0         $self->{'-dokern'} = 1 if $opts{'dokern'};
156              
157 0           return $self;
158             } # end of new()
159              
160             sub readPFAPFB {
161 0     0 0   my ($self, $file) = @_;
162 0           my ($l1,$l2,$l3, $stream, $t1stream, @lines, $line, $head, $body, $tail);
163              
164 0 0         die "Cannot find PFA/PFB font file '$file' ..." unless -f $file;
165              
166 0           my $l = -s $file;
167 0           $l1 = $l2 = $l3 = 0;
168 0           $head = $body = $tail = '';
169              
170 0           my $type = 'pfa';
171 0 0         if ($file =~ m/\.pfb$/i) {
    0          
172 0           $type = 'pfb';
173             } elsif ($file =~ m/\.t1$/i) {
174 0           $type = 't1';
175             }
176              
177 0 0         open(my $inf, "<", $file) or die "$!: $file";
178 0           binmode($inf,':raw');
179 0           read($inf, $line, 2); # read 2 bytes to check header
180 0           @lines = unpack('C*', $line);
181              
182 0 0 0       if ($lines[0] == 0x80 && $lines[1] == 1) { # .pfb
    0 0        
    0 0        
183             # first 6 bytes are 80 01, 4 byte LSB $l1 head length
184 0           read($inf, $line, 4);
185 0           $l1 = unpack('V', $line); # length of head
186 0           seek($inf, $l1, 1);
187 0           read($inf, $line, 2);
188 0           @lines = unpack('C*', $line);
189             # at start of binary body, 6 bytes 80 01, 4 byte LSB $l2 body length
190 0 0 0       if ($lines[0] == 0x80 && $lines[1] == 2) {
191 0           read($inf, $line, 4);
192 0           $l2 = unpack('V', $line);
193             } else {
194 0           die "Corrupt PFB in file '$file' at marker='2'.";
195             }
196 0           seek($inf, $l2, 1);
197 0           read($inf, $line, 2);
198 0           @lines = unpack('C*', $line);
199             # after body, 6 bytes 80 01, 4 byte LSB $l3 tail length
200 0 0 0       if ($lines[0] == 0x80 && $lines[1] == 1) {
201 0           read($inf, $line, 4);
202 0           $l3 = unpack('V', $line);
203             } else {
204 0           die "Corrupt PFB in file '$file' at marker='3'.";
205             }
206 0           seek($inf, 0, 0);
207 0           @lines = <$inf>;
208 0           $stream = join('', @lines);
209             # each section, skip over 80 01, length; read in length of section
210 0           $t1stream = substr($stream, 6, $l1);
211 0           $t1stream .= substr($stream, 12+$l1, $l2);
212 0           $t1stream .= substr($stream, 18+$l1+$l2, $l3);
213              
214             } elsif ($line eq '%!' && $type eq 'pfa') {
215 0           seek($inf, 0, 0);
216 0           while ($line = <$inf>) {
217 0 0         if (!$l1) { # $head empty or not complete yet?
    0          
218 0           $head .= $line; # up through and including currentfile eexec
219 0 0         if ($line=~/eexec$/) {
220 0           chomp($head);
221 0           $head .= "\x0d";
222 0           $l1 = length($head);
223             }
224             } elsif (!$l2) { # $body empty or not complete yet?
225 0 0         if ($line =~ /^0+$/) { # at block of 0's, marking end of body
226 0           $l2 = length($body);
227 0           $tail = $line;
228             } else {
229 0           chomp($line);
230 0           $body .= pack('H*', $line); # binary form of hex codes
231             }
232             } else { # rest goes into the $tail
233 0           $tail .= $line;
234             }
235             }
236 0           $l3 = length($tail);
237             # head = individual lines (^M terminated) with settings list
238             # body = one long string of bytes (binary)
239             # tail = 8 lines x 64 0's ^M terminated, cleartomark (no ^M)
240 0           $t1stream = "$head$body$tail";
241              
242             } elsif ($line eq '%!' && $type eq 't1') {
243             # .t1
244 0           my $pos;
245 0           seek($inf, 0, 0);
246 0           while (1) { # head
247 0           read($inf, $line, 200);
248 0           $head .= $line;
249 0           $pos = index($head, "currentfile eexec\x0D");
250 0 0         if ($pos > 0) {
251             # found end of head, so split there
252 0           $body = substr($head, $pos+18);
253 0           $head = substr($head, 0, $pos+18);
254 0           $l1 = length($head);
255 0           last;
256             }
257             }
258 0           while (1) { # body
259 0           read($inf, $line, 200);
260 0           $body .= $line;
261             # 1111111111222222222233333333334444444444555555555566666
262             # 1234567890123456789012345678901234567890123456789012345678901234
263 0           $pos = index($body, "0000000000000000000000000000000000000000000000000000000000000000");
264 0 0         if ($pos > 0) {
265             # found end of body, so split there
266 0           $tail = substr($body, $pos);
267 0           $body = substr($body, 0, $pos);
268 0           $l2 = length($body);
269 0           last;
270             }
271             }
272 0           while (1) { # remainder into tail
273 0           read($inf, $line, 200);
274 0           $tail .= $line;
275 0 0         if (length($line) == 0) {
276             # found end of tail
277 0           $l3 = length($tail);
278 0           last;
279             }
280             }
281              
282             # head = individual lines (^M terminated) with settings list
283             # body = one long string of bytes (binary)
284             # tail = 8 lines x 64 0's ^M terminated, cleartomark (no ^M)
285 0           $t1stream = "$head$body$tail";
286              
287             } else {
288 0           die "Unsupported font-format in file '$file' at marker='1'.";
289             }
290 0           close($inf);
291              
292 0           return($l1,$l2,$l3, $t1stream);
293             } # end of readPFAPFB()
294              
295             # $datahashref = $self->readAFM( $afmfile );
296              
297             sub readAFM {
298 0     0 0   my ($self, $file) = @_;
299              
300 0           my $data = {};
301 0           $data->{'wx'} = {};
302 0           $data->{'bbox'} = {};
303 0           $data->{'char'} = [];
304 0           $data->{'firstchar'} = 255;
305 0           $data->{'lastchar'} = 0;
306              
307 0 0         if (! -e $file) {
308 0           die "File='$file' not found.";
309             }
310 0 0         open(my $afmf, "<", $file) or die "Can't find the AFM file for $file";
311 0           local($/, $_) = ("\n", undef); # ensure correct $INPUT_RECORD_SEPARATOR
312 0           while ($_ = <$afmf>) {
313 0 0         if (/^StartCharMetrics/ .. /^EndCharMetrics/) {
    0          
    0          
314             # only lines that start with "C" or "CH" are parsed
315 0 0         next unless $_ =~ /^CH?\s/;
316 0           my ($ch) = $_ =~ /^CH?\s+(\d+)\s*;/;
317 0   0       $ch = $ch || 0;
318 0           my ($name) = $_ =~ /\bN\s+(\.?\w+)\s*;/;
319 0           my ($wx) = $_ =~ /\bWX\s+(\d+)\s*;/;
320 0           my ($bbox) = $_ =~ /\bB\s+([^;]+);/;
321 0           $bbox =~ s/\s+$//;
322             # Should also parse ligature data (format: L successor ligature)
323 0           $data->{'avgwidth2'} += $wx ;
324 0 0 0       $data->{'maxwidth'} = ($data->{'maxwidth'}||0) < $wx? $wx: $data->{'maxwidth'}||0;
      0        
325 0           $data->{'wx'}->{$name} = $wx;
326 0           $data->{'bbox'}->{$name} = [split(/\s+/,$bbox)];
327 0 0         if ($ch > 0) {
328 0           $data->{'char'}->[$ch] = $name;
329             }
330 0 0         $data->{'lastchar'} = $data->{'lastchar'} < $ch? $ch: $data->{'lastchar'};
331 0 0         $data->{'firstchar'} = $data->{'firstchar'} > $ch? $ch: $data->{'firstchar'};
332 0           next;
333             } elsif (/^StartKernData/ .. /^EndKernData/) {
334 0   0       $data->{'kern'} ||= {};
335 0 0         if ($_ =~ m|^KPX\s+(\S+)\s+(\S+)\s+(\S+)\s*$|i) {
336 0           $data->{'kern'}->{"$1:$2"} = $3;
337             }
338             } elsif (/^StartComposites/ .. /^EndComposites/) {
339 0   0       $data->{'comps'} ||= {};
340 0 0         if ($_ =~ m|^CC\s+(\S+)\s+(\S+)\s+;|i) {
341 0           my ($name, $comp) = ($1, $2);
342 0           my @cv = split(/;/, $_);
343 0           shift @cv;
344 0           my $rng = [];
345 0           foreach (1..$comp) {
346 0           my @c1 = split(/\s+/,shift @cv);
347 0           push @{$rng}, $c1[1],$c1[2],$c1[3];
  0            
348             }
349 0           $data->{'comps'}->{$name} = $rng;
350             }
351             }
352 0 0         last if $_ =~ /^EndFontMetrics/;
353 0 0         if (/(^\w+)\s+(.*)/) {
354 0           my($key, $val) = ($1, $2);
355 0           $key = lc($key);
356 0 0         if (defined $data->{$key}) {
357             # $data->{$key} = [ $data->{$key} ] unless ref $data->{$key};
358             # push(@{$data->{$key}}, $val);
359             } else {
360 0           $val =~ s/[\x00\x1f]+//g;
361 0           $data->{$key} = $val;
362             }
363             } else {
364             ## print STDERR "Can't parse: $_";
365             }
366             }
367 0           close($afmf);
368 0 0         unless (exists $data->{'wx'}->{'.notdef'}) {
369 0           $data->{'wx'}->{'.notdef'} = 0;
370 0           $data->{'bbox'}{'.notdef'} = [0, 0, 0, 0];
371             }
372              
373 0           $data->{'avgwidth2'} /= scalar keys %{$data->{'bbox'}} ;
  0            
374 0           $data->{'avgwidth2'} = int($data->{'avgwidth2'});
375              
376 0           $data->{'fontname'} =~ s/[\x00-\x20]+//og;
377             ## $data->{'fontname'} =~ s/[^A-Za-z0-9]+//og;
378              
379 0 0         if (defined $data->{'fullname'}) {
380 0           $data->{'altname'} = $data->{'fullname'};
381             } else {
382 0           $data->{'altname'} = $data->{'familyname'};
383 0 0         $data->{'altname'} .= ' Italic' if $data->{'italicangle'} < 0;
384 0 0         $data->{'altname'} .= ' Oblique' if $data->{'italicangle'} > 0;
385 0           $data->{'altname'} .= ' '.$data->{'weight'};
386             }
387 0           $data->{'apiname'} = $data->{'altname'};
388 0           $data->{'altname'} =~ s/[^A-Za-z0-9]+//og;
389              
390 0           $data->{'subname'} = $data->{'weight'};
391 0 0         $data->{'subname'} .= ' Italic' if $data->{'italicangle'} < 0;
392 0 0         $data->{'subname'} .= ' Oblique' if $data->{'italicangle'} > 0;
393 0           $data->{'subname'} =~ s/[^A-Za-z0-9]+//og;
394              
395 0   0       $data->{'missingwidth'} ||= $data->{'avgwidth2'};
396              
397 0           $data->{'issymbol'} = 0;
398 0           $data->{'fontbbox'} = [ split(/\s+/,$data->{'fontbbox'}) ];
399              
400 0           $data->{'apiname'} = join('', map { ucfirst(lc(substr($_, 0, 2))) } split m/[^A-Za-z0-9\s]+/, $data->{'apiname'});
  0            
401              
402 0           $data->{'flags'} = 34;
403              
404 0   0       $data->{'uni'} ||= [];
405 0           foreach my $n (0..255) {
406 0   0       $data->{'uni'}->[$n] = uniByName($data->{'char'}->[$n] || '.notdef') || 0;
407             }
408 0           delete $data->{'bbox'};
409              
410 0           return $data;
411             } # end of readAFM()
412              
413             sub readPFM {
414 0     0 0   my ($self, $file) = @_;
415              
416 0 0         if (! -e $file) {
417 0           die "pfmfile='$file' not found.";
418             }
419 0           my $fh = IO::File->new();
420 0           my $data = {};
421              
422 0           $data->{'issymbol'} = 0;
423              
424 0           $data->{'wx'} = {};
425 0           $data->{'bbox'} = {};
426 0           $data->{'kern'} = {};
427 0           $data->{'char'} = [];
428              
429 0           my $buf;
430 0 0         open($fh, "<", $file) || return;
431 0           binmode($fh, ':raw');
432 0           read($fh, $buf, 117 + 30);
433              
434 0           my %df;
435             # Packing structure for PFM Header
436             ( $df{'Version'},
437             $df{'Size'},
438             $df{'Copyright'},
439             $df{'Type'},
440             $df{'Point'},
441             $df{'VertRes'},
442             $df{'HorizRes'},
443             $df{'Ascent'},
444             $df{'InternalLeading'},
445             $df{'ExternalLeading'},
446             $df{'Italic'},
447             $df{'Underline'},
448             $df{'StrikeOut'},
449             $df{'Weight'},
450             #define FW_DONTCARE 0
451             #define FW_THIN 100
452             #define FW_EXTRALIGHT 200
453             #define FW_ULTRALIGHT FW_EXTRALIGHT
454             #define FW_LIGHT 300
455             #define FW_NORMAL 400
456             #define FW_REGULAR 400
457             #define FW_MEDIUM 500
458             #define FW_SEMIBOLD 600
459             #define FW_DEMIBOLD FW_SEMIBOLD
460             #define FW_BOLD 700
461             #define FW_EXTRABOLD 800
462             #define FW_ULTRABOLD FW_EXTRABOLD
463             #define FW_HEAVY 900
464             #define FW_BLACK FW_HEAVY
465             $df{'CharSet'},
466             #define ANSI_CHARSET 0
467             #define DEFAULT_CHARSET 1
468             #define SYMBOL_CHARSET 2
469             #define SHIFTJIS_CHARSET 128
470             #define HANGEUL_CHARSET 129
471             #define HANGUL_CHARSET 129
472             #define GB2312_CHARSET 134
473             #define CHINESEBIG5_CHARSET 136
474             #define GREEK_CHARSET 161
475             #define TURKISH_CHARSET 162
476             #define HEBREW_CHARSET 177
477             #define ARABIC_CHARSET 178
478             #define BALTIC_CHARSET 186
479             #define RUSSIAN_CHARSET 204
480             #define THAI_CHARSET 222
481             #define EASTEUROPE_CHARSET 238
482             #define OEM_CHARSET 255
483             #define JOHAB_CHARSET 130
484             #define VIETNAMESE_CHARSET 163
485             #define MAC_CHARSET 77
486             #define BALTIC_CHARSET 186
487             #define JOHAB_CHARSET 130
488             #define VIETNAMESE_CHARSET 163
489             $df{'PixWidth'},
490             $df{'PixHeight'},
491             $df{'PitchAndFamily'},
492             #define DEFAULT_PITCH 0
493             #define FIXED_PITCH 1
494             #define VARIABLE_PITCH 2
495             #define MONO_FONT 8
496             #define FF_DECORATIVE 80
497             #define FF_DONTCARE 0
498             #define FF_MODERN 48
499             #define FF_ROMAN 16
500             #define FF_SCRIPT 64
501             #define FF_SWISS 32
502             $df{'AvgWidth'},
503             $df{'MaxWidth'},
504             $df{'FirstChar'},
505             $df{'LastChar'},
506             $df{'DefaultChar'},
507             $df{'BreakChar'},
508             $df{'WidthBytes'},
509             $df{'Device'},
510             $df{'Face'},
511             $df{'BitsPointer'},
512             $df{'BitsOffset'},
513             $df{'SizeFields'}, # Two bytes, the size of extension section
514             $df{'ExtMetricsOffset'}, # Four bytes, offset value to the 'Extended Text Metrics' section
515             $df{'ExtentTable'}, # Four bytes Offset value to the Extent Table
516             $df{'OriginTable'}, # Four bytes 0
517             $df{'PairKernTable'}, # Four bytes 0
518             $df{'TrackKernTable'}, # Four bytes 0
519             $df{'DriverInfo'}, # Four bytes Offset value to the PostScript font name string
520 0           $df{'Reserved'}, # Four bytes 0
521             ) = unpack("vVa60vvvvvvvCCCvCvvCvvCCCCvVVVV vVVVVVVV",$buf); # PFM Header + Ext
522              
523 0           seek($fh, $df{Device}, 0);
524 0           read($fh, $buf, 250);
525              
526 0           ($df{'postScript'}) = unpack("Z*", $buf);
527 0           $buf = substr($buf, length($df{'postScript'})+1, 250);
528 0           ($df{'windowsName'}) = unpack("Z*", $buf);
529 0           $buf = substr($buf, length($df{'windowsName'})+1, 250);
530 0           ($df{'psName'}) = unpack("Z*", $buf);
531              
532 0           seek($fh, $df{'ExtMetricsOffset'}, 0);
533 0           read($fh, $buf, 52);
534              
535             ( $df{'etmSize'},
536             $df{'PointSize'},
537             $df{'Orientation'},
538             $df{'MasterHeight'},
539             $df{'MinScale'},
540             $df{'MaxScale'},
541             $df{'MasterUnits'},
542             $df{'CapHeight'},
543             $df{'xHeight'},
544             $df{'LowerCaseAscent'},
545             $df{'LowerCaseDescent'},
546             $df{'Slant'},
547             $df{'SuperScript'},
548             $df{'SubScript'},
549             $df{'SuperScriptSize'},
550             $df{'SubScriptSize'},
551             $df{'UnderlineOffset'},
552             $df{'UnderlineWidth'},
553             $df{'DoubleUpperUnderlineOffset'},
554             $df{'DoubleLowerUnderlineOffset'},
555             $df{'DoubleUpperUnderlineWidth'},
556             $df{'DoubleLowerUnderlineWidth'},
557             $df{'StrikeOutOffset'},
558             $df{'StrikeOutWidth'},
559             $df{'KernPairs'},
560 0           $df{'KernTracks'} ) = unpack('v*', $buf);
561              
562 0           $data->{'fontname'} = $df{'psName'};
563 0           $data->{'fontname'} =~ s/[^A-Za-z0-9]+//og;
564 0           $data->{'apiname'} = join('', map { ucfirst(lc(substr($_, 0, 2))) } split m/[^A-Za-z0-9\s]+/, $df{'windowsName'});
  0            
565              
566 0           $data->{'upem'} = 1000;
567              
568 0           $data->{'fontbbox'} = [-100,-100, $df{'MaxWidth'},$df{'Ascent'}];
569              
570 0           $data->{'stemv'} = 0;
571 0           $data->{'stemh'} = 0;
572              
573 0   0       $data->{'lastchar'} = $df{'LastChar'}||0; # running max
574 0   0       $data->{'firstchar'} = $df{'FirstChar'}||255; # running min
575              
576 0           $data->{'missingwidth'} = $df{'AvgWidth'};
577 0           $data->{'maxwidth'} = $df{'MaxWidth'};
578 0           $data->{'ascender'} = $df{'Ascent'};
579 0           $data->{'descender'} = -$df{'LowerCaseDescent'};
580              
581 0           $data->{'flags'} = 0;
582             # FixedPitch 1
583 0 0 0       $data->{'flags'} |= 1 if (($df{'PitchAndFamily'} & 1) || ($df{'PitchAndFamily'} & 8)) && !($df{'PitchAndFamily'} & 2);
      0        
584             # Serif 2
585 0 0 0       $data->{'flags'} |= 2 if ($df{'PitchAndFamily'} & 16) && !($df{'PitchAndFamily'} & 32);
586             # Symbolic 4
587 0 0         $data->{'flags'} |= 4 if $df{'PitchAndFamily'} & 80;
588             # Script 8
589 0 0         $data->{'flags'} |= 8 if $df{'PitchAndFamily'} & 64;
590             # Nonsymbolic 32
591 0 0         $data->{'flags'} |= 32 unless $df{'PitchAndFamily'} & 80;
592             # Italic 64
593 0 0         $data->{'flags'} |= 64 if $df{'Italic'};
594              
595             #bit 17 AllCap
596             #bit 18 SmallCap
597             #bit 19 ForceBold
598              
599 0           $data->{'capheight'} = $df{'CapHeight'};
600 0           $data->{'xheight'} = $df{'xHeight'};
601              
602 0           $data->{'uni'} = [ unpack('U*', decode('cp1252', pack('C*',(0..255)))) ];
603 0 0         $data->{'char'} = [ map { nameByUni($_) || '.notdef' } @{$data->{'uni'}} ];
  0            
  0            
604              
605 0           $data->{'italicangle'} = -12*$df{'Italic'};
606 0   0       $data->{'isfixedpitch'} = ($df{'PitchAndFamily'} & 8) || ($df{'PitchAndFamily'} & 1);
607 0           $data->{'underlineposition'} = -$df{'UnderlineOffset'};
608 0           $data->{'underlinethickness'} = $df{'UnderlineWidth'};
609              
610 0           seek($fh, $df{'ExtentTable'}, 0);
611              
612 0           foreach my $k ($df{'FirstChar'} .. $df{'LastChar'}) {
613 0           read($fh, $buf, 2);
614 0           my ($wx) = unpack('v', $buf);
615 0           $data->{'wx'}->{$data->{'char'}->[$k]} = $wx;
616             # print STDERR "e: c=$k n='".$data->{'char'}->[$k]."' wx='$wx'\n";
617             }
618 0           $data->{'pfm'} = \%df;
619 0           close($fh);
620              
621 0           return $data;
622             } # end of readPFM()
623              
624             sub readXFM {
625 0     0 0   my ($class, $xfmfile) = @_;
626              
627 0 0         die "Cannot find font '$xfmfile' ..." unless -f $xfmfile;
628              
629 0           my $data = {};
630              
631 0           return $data;
632             }
633              
634             1;