File Coverage

blib/lib/PDF/Builder/Resource/CIDFont/TrueType/FontFile.pm
Criterion Covered Total %
statement 27 485 5.5
branch 0 274 0.0
condition 0 81 0.0
subroutine 9 24 37.5
pod 2 14 14.2
total 38 878 4.3


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::CIDFont::TrueType::FontFile;
2              
3 1     1   5 use base 'PDF::Builder::Basic::PDF::Dict';
  1         2  
  1         93  
4              
5 1     1   5 use strict;
  1         1  
  1         14  
6 1     1   3 use warnings;
  1         1  
  1         50  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.028'; # manually update whenever code is changed
10              
11 1     1   4 use Carp;
  1         2  
  1         97  
12 1     1   6 use Encode qw(:all);
  1         1  
  1         282  
13 1     1   702 use Font::TTF::Font;
  1         3848  
  1         36  
14 1     1   6 use POSIX qw(ceil floor);
  1         2  
  1         9  
15              
16 1     1   80 use PDF::Builder::Util;
  1         2  
  1         98  
17 1     1   3 use PDF::Builder::Basic::PDF::Utils;
  1         3  
  1         6074  
18              
19             our $cmap = {};
20              
21             # for new() if not using find_ms() or .cmap files
22             # may be overridden fully or partially by cmaps option
23             # [0] is Windows list, [1] is non-Windows list Platform/Encoding
24             # can substitute 'find_ms' instead of a list of P/E
25             # suggested default list by Alfred Reibenschuh (original PDF::API2 author)
26             my @default_CMap = ('0/6 3/10 0/4 3/1 0/3', '0/6 0/4 3/10 0/3 3/1');
27              
28             =head1 NAME
29              
30             PDF::Builder::Resource::CIDFont::TrueType::FontFile - Additional code support for TT font files
31              
32             Inherits from L<PDF::Builder::Basic::PDF::Dict>
33              
34             =cut
35              
36             # identical routine in Resource/CIDFont/CJKFont.pm
37             sub _look_for_cmap {
38 0     0     my $map = shift;
39 0           my $fname = lc($map);
40              
41 0           $fname =~ s/[^a-z0-9]+//gi;
42 0 0         return ({%{$cmap->{$fname}}}) if defined $cmap->{$fname};
  0            
43 0           eval "require 'PDF/Builder/Resource/CIDFont/CMap/$fname.cmap'"; ## no critic
44 0 0         unless ($@) {
45 0           return {%{$cmap->{$fname}}};
  0            
46             } else {
47 0           die "requested cmap '$map' not installed ";
48             }
49             }
50              
51             sub readcffindex {
52 0     0 0   my ($fh, $off, $buf) = @_;
53              
54 0           my @idx = ();
55 0           my $index = [];
56 0           seek($fh, $off, 0);
57 0           read($fh, $buf, 3);
58 0           my ($count, $offsize) = unpack('nC', $buf);
59 0           foreach (0 .. $count) {
60 0           read($fh, $buf, $offsize);
61 0           $buf = substr("\x00\x00\x00$buf", -4, 4);
62 0           my $id = unpack('N', $buf);
63 0           push(@idx, $id);
64             }
65 0           my $dataoff = tell($fh)-1;
66              
67 0           foreach my $i (0 .. $count-1) {
68 0           push(@{$index}, { 'OFF' => $dataoff+$idx[$i],
  0            
69             'LEN' => $idx[$i+1]-$idx[$i] });
70             }
71 0           return $index;
72             }
73              
74             sub readcffdict {
75 0     0 0   my ($fh, $off, $len, $foff, $buf) = @_;
76              
77 0           my @idx = ();
78 0           my $dict = {};
79 0           seek($fh, $off, 0);
80 0           my @st = ();
81 0           while (tell($fh) < ($off+$len)) {
82 0           read($fh, $buf, 1);
83 0           my $b0 = unpack('C', $buf);
84 0           my $v = '';
85              
86 0 0         if ($b0 == 12) { # two byte commands
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
87 0           read($fh, $buf, 1);
88 0           my $b1 = unpack('C', $buf);
89 0 0         if ($b1 == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
90 0           $dict->{'Copyright'} = { 'SID' => splice(@st, -1) };
91             } elsif ($b1 == 1) {
92 0           $dict->{'isFixedPitch'} = splice(@st, -1);
93             } elsif ($b1 == 2) {
94 0           $dict->{'ItalicAngle'} = splice(@st, -1);
95             } elsif ($b1 == 3) {
96 0           $dict->{'UnderlinePosition'} = splice(@st, -1);
97             } elsif ($b1 == 4) {
98 0           $dict->{'UnderlineThickness'} = splice(@st, -1);
99             } elsif ($b1 == 5) {
100 0           $dict->{'PaintType'} = splice(@st, -1);
101             } elsif ($b1 == 6) {
102 0           $dict->{'CharstringType'} = splice(@st, -1);
103             } elsif ($b1 == 7) {
104 0           $dict->{'FontMatrix'} = [ splice(@st, -4) ];
105             } elsif ($b1 == 8) {
106 0           $dict->{'StrokeWidth'} = splice(@st, -1);
107             } elsif ($b1 == 20) {
108 0           $dict->{'SyntheticBase'} = splice(@st, -1);
109             } elsif ($b1 == 21) {
110 0           $dict->{'PostScript'} = { 'SID' => splice(@st, -1) };
111             } elsif ($b1 == 22) {
112 0           $dict->{'BaseFontName'} = { 'SID' => splice(@st, -1) };
113             } elsif ($b1 == 23) {
114 0           $dict->{'BaseFontBlend'} = [ splice(@st, 0) ];
115             } elsif ($b1 == 24) {
116 0           $dict->{'MultipleMaster'} = [ splice(@st, 0) ];
117             } elsif ($b1 == 25) {
118 0           $dict->{'BlendAxisTypes'} = [ splice(@st, 0) ];
119             } elsif ($b1 == 30) {
120 0           $dict->{'ROS'} = [ splice(@st, -3) ];
121             } elsif ($b1 == 31) {
122 0           $dict->{'CIDFontVersion'} = splice(@st, -1);
123             } elsif ($b1 == 32) {
124 0           $dict->{'CIDFontRevision'} = splice(@st, -1);
125             } elsif ($b1 == 33) {
126 0           $dict->{'CIDFontType'} = splice(@st, -1);
127             } elsif ($b1 == 34) {
128 0           $dict->{'CIDCount'} = splice(@st, -1);
129             } elsif ($b1 == 35) {
130 0           $dict->{'UIDBase'} = splice(@st, -1);
131             } elsif ($b1 == 36) {
132 0           $dict->{'FDArray'} = { 'OFF' => $foff+splice(@st, -1) };
133             } elsif ($b1 == 37) {
134 0           $dict->{'FDSelect'} = { 'OFF' => $foff+splice(@st, -1) };
135             } elsif ($b1 == 38) {
136 0           $dict->{'FontName'} = { 'SID' => splice(@st, -1) };
137             } elsif ($b1 == 39) {
138 0           $dict->{'Chameleon'} = splice(@st, -1);
139             }
140 0           next;
141             } elsif ($b0 < 28) { # commands
142 0 0         if ($b0 == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
143 0           $dict->{'Version'} = { 'SID' => splice(@st, -1) };
144             } elsif ($b0 == 1) {
145 0           $dict->{'Notice'} = { 'SID' => splice(@st, -1) };
146             } elsif ($b0 == 2) {
147 0           $dict->{'FullName'} = { 'SID' => splice(@st, -1) };
148             } elsif ($b0 == 3) {
149 0           $dict->{'FamilyName'} = { 'SID' => splice(@st, -1) };
150             } elsif ($b0 == 4) {
151 0           $dict->{'Weight'} = { 'SID' => splice(@st, -1) };
152             } elsif ($b0 == 5) {
153 0           $dict->{'FontBBX'} = [ splice(@st, -4) ];
154             } elsif ($b0 == 13) {
155 0           $dict->{'UniqueID'} = splice(@st, -1);
156             } elsif ($b0 == 14) {
157 0           $dict->{'XUID'} = [ splice(@st, 0) ];
158             } elsif ($b0 == 15) {
159 0           $dict->{'CharSet'} = { 'OFF' => $foff+splice(@st, -1) };
160             } elsif ($b0 == 16) {
161 0           $dict->{'Encoding'} = { 'OFF' => $foff+splice(@st, -1) };
162             } elsif ($b0 == 17) {
163 0           $dict->{'CharStrings'} = { 'OFF' => $foff+splice(@st, -1) };
164             } elsif ($b0 == 18) {
165 0           $dict->{'Private'} = { 'LEN' => splice(@st, -1),
166             'OFF' => $foff+splice(@st, -1) };
167             }
168 0           next;
169             } elsif ($b0 == 28) { # int16
170 0           read($fh, $buf, 2);
171 0           $v = unpack('n', $buf);
172 0 0         $v = -(0x10000 - $v) if $v > 0x7fff;
173             # alt: $v = unpack('n!', $buf);
174             } elsif ($b0 == 29) { # int32
175 0           read($fh, $buf, 4);
176 0           $v = unpack('N', $buf);
177 0 0         $v = -$v + 0xffffffff+1 if $v > 0x7fffffff;
178             # alt: $v = unpack('N!', $buf);
179             } elsif ($b0 == 30) { # float
180 0           my $e = 1;
181 0           while ($e) {
182 0           read($fh, $buf, 1);
183 0           my $v0 = unpack('C', $buf);
184 0           foreach my $m ($v0 >> 8, $v0&0xf) {
185 0 0         if ($m < 10) {
    0          
    0          
    0          
    0          
    0          
186 0           $v .= $m;
187             } elsif ($m == 10) {
188 0           $v .= '.';
189             } elsif ($m == 11) {
190 0           $v .= 'E+';
191             } elsif ($m == 12) {
192 0           $v .= 'E-';
193             } elsif ($m == 14) {
194 0           $v .= '-';
195             } elsif ($m == 15) {
196 0           $e = 0;
197 0           last;
198             }
199             }
200             }
201             } elsif ($b0 == 31) { # command
202 0           $v = "c=$b0";
203 0           next;
204             } elsif ($b0 < 247) { # 1 byte signed
205 0           $v = $b0 - 139;
206             } elsif ($b0 < 251) { # 2 byte plus
207 0           read($fh, $buf, 1);
208 0           $v = unpack('C', $buf);
209 0           $v = ($b0 - 247)*256 + ($v + 108);
210             } elsif ($b0 < 255) { # 2 byte minus
211 0           read($fh, $buf, 1);
212 0           $v = unpack('C', $buf);
213 0           $v = -($b0 - 251)*256 - $v - 108;
214             }
215 0           push(@st, $v);
216             }
217              
218 0           return $dict;
219             }
220              
221             sub read_kern_table {
222 0     0 0   my ($font, $upem, $self) = @_;
223 0           my $fh = $font->{' INFILE'};
224 0           my $data;
225             my $buf;
226              
227 0 0         return unless $font->{'kern'};
228              
229 0           seek($fh, $font->{'kern'}->{' OFFSET'}+2, 0);
230 0           read($fh, $buf, 2);
231 0           my $num = unpack('n', $buf);
232 0           foreach my $n (1 .. $num) {
233 0           read($fh, $buf, 6);
234 0           my ($ver, $len, $cov) = unpack('n3', $buf);
235 0           $len -= 6;
236 0           my $fmt = $cov >> 8;
237 0 0         if ($fmt == 0) {
    0          
238 0   0       $data ||= {};
239 0           read($fh, $buf, 8);
240 0           my $nc = unpack('n', $buf);
241 0           foreach (1 .. $nc) {
242 0           read($fh, $buf, 6);
243 0           my ($idx1, $idx2, $val) = unpack('n2n!', $buf);
244             # alt: unpack('nnn', $buf);
245 0 0         $val -= 65536 if $val > 32767;
246 0 0         $val = $val<0? -floor($val*1000/$upem): -ceil($val*1000/$upem);
247 0 0         if ($val != 0) {
248 0           $data->{"$idx1:$idx2"} = $val;
249             $data->{join(':',
250             ($self->data()->{'g2n'}->[$idx1] // ''),
251 0   0       ($self->data()->{'g2n'}->[$idx2] // '')
      0        
252             )} = $val;
253             }
254             }
255             } elsif ($fmt==2) {
256 0           read($fh, $buf, $len);
257             } else {
258 0           read($fh, $buf, $len);
259             }
260             }
261 0           return $data;
262             }
263              
264             sub readcffstructs {
265 0     0 0   my $font = shift;
266              
267 0           my $fh = $font->{' INFILE'};
268 0           my $data = {};
269             # read CFF table
270 0           seek($fh, $font->{'CFF '}->{' OFFSET'}, 0);
271 0           my $buf;
272 0           read($fh, $buf, 4);
273 0           my ($cffmajor, $cffminor, $cffheadsize, $cffglobaloffsize) = unpack('C4', $buf);
274              
275 0           $data->{'name'} = readcffindex($fh, $font->{'CFF '}->{' OFFSET'}+$cffheadsize);
276 0           foreach my $dict (@{$data->{'name'}}) {
  0            
277 0           seek($fh, $dict->{'OFF'}, 0);
278 0           read($fh, $dict->{'VAL'}, $dict->{'LEN'});
279             }
280              
281 0           $data->{'topdict'} = readcffindex($fh, $data->{'name'}->[-1]->{'OFF'}+$data->{'name'}->[-1]->{'LEN'});
282 0           foreach my $dict (@{$data->{'topdict'}}) {
  0            
283 0           $dict->{'VAL'} = readcffdict($fh, $dict->{'OFF'}, $dict->{LEN}, $font->{'CFF '}->{' OFFSET'});
284             }
285              
286 0           $data->{'string'} = readcffindex($fh, $data->{'topdict'}->[-1]->{'OFF'}+$data->{'topdict'}->[-1]->{'LEN'});
287 0           foreach my $dict (@{$data->{'string'}}) {
  0            
288 0           seek($fh, $dict->{'OFF'}, 0);
289 0           read($fh, $dict->{'VAL'}, $dict->{'LEN'});
290             }
291 0           push(@{$data->{'string'}}, { 'VAL' => '001.000' });
  0            
292 0           push(@{$data->{'string'}}, { 'VAL' => '001.001' });
  0            
293 0           push(@{$data->{'string'}}, { 'VAL' => '001.002' });
  0            
294 0           push(@{$data->{'string'}}, { 'VAL' => '001.003' });
  0            
295 0           push(@{$data->{'string'}}, { 'VAL' => 'Black' });
  0            
296 0           push(@{$data->{'string'}}, { 'VAL' => 'Bold' });
  0            
297 0           push(@{$data->{'string'}}, { 'VAL' => 'Book' });
  0            
298 0           push(@{$data->{'string'}}, { 'VAL' => 'Light' });
  0            
299 0           push(@{$data->{'string'}}, { 'VAL' => 'Medium' });
  0            
300 0           push(@{$data->{'string'}}, { 'VAL' => 'Regular' });
  0            
301 0           push(@{$data->{'string'}}, { 'VAL' => 'Roman' });
  0            
302 0           push(@{$data->{'string'}}, { 'VAL' => 'Semibold' });
  0            
303              
304 0           foreach my $dict (@{$data->{'topdict'}}) {
  0            
305 0           foreach my $k (keys %{$dict->{'VAL'}}) {
  0            
306 0           my $dt = $dict->{'VAL'}->{$k};
307 0 0         if ($k eq 'ROS') {
308 0           $dict->{'VAL'}->{$k}->[0] = $data->{'string'}->[$dict->{'VAL'}->{$k}->[0]-391]->{'VAL'};
309 0           $dict->{'VAL'}->{$k}->[1] = $data->{'string'}->[$dict->{'VAL'}->{$k}->[1]-391]->{'VAL'};
310 0           next;
311             }
312 0 0 0       next unless ref($dt) eq 'HASH' && defined $dt->{'SID'};
313 0 0         if ($dt->{'SID'} >= 379) {
314 0           $dict->{'VAL'}->{$k} = $data->{'string'}->[$dt->{'SID'}-391]->{'VAL'};
315             }
316             }
317             }
318 0           my $dict = {};
319 0           foreach my $k (qw[ CIDCount CIDFontVersion FamilyName FontBBX FullName ROS Weight XUID ]) {
320 0 0         $dict->{$k} = $data->{'topdict'}->[0]->{'VAL'}->{$k} if defined $data->{'topdict'}->[0]->{'VAL'}->{$k};
321             }
322 0           return $dict;
323             }
324              
325             sub new {
326 0     0 1   my ($class, $pdf, $file, %opts) = @_;
327             # copy dashed option names to preferred undashed names
328 0 0 0       if (defined $opts{'-isocmap'} && !defined $opts{'isocmap'}) { $opts{'isocmap'} = delete($opts{'-isocmap'}); }
  0            
329 0 0 0       if (defined $opts{'-debug'} && !defined $opts{'debug'}) { $opts{'debug'} = delete($opts{'-debug'}); }
  0            
330 0 0 0       if (defined $opts{'-cmaps'} && !defined $opts{'cmaps'}) { $opts{'cmaps'} = delete($opts{'-cmaps'}); }
  0            
331 0 0 0       if (defined $opts{'-usecmf'} && !defined $opts{'usecmf'}) { $opts{'usecmf'} = delete($opts{'-usecmf'}); }
  0            
332             # ttfont() should have already set 'embed' properly, so ignore noembed too
333             #if (defined $opts{'-noembed'} && !defined $opts{'noembed'}) { $opts{'noembed'} = delete($opts{'-noembed'}); }
334             #if (defined $opts{'-embed'} && !defined $opts{'embed'}) { $opts{'embed'} = delete($opts{'-embed'}); }
335              
336 0           my $data = {};
337             # some debug settings
338             #$opts{'debug'} = 1;
339             #$opts{'cmaps'} = '0/6, 0/4, 3/10, 0/3, 3/1';
340             #$opts{'cmaps'} = '7/8; 8/7'; # invalid P/E, should use find_ms instead
341             #$opts{'cmaps'} = 'find_ms; find_ms ';
342             #$opts{'usecmf'} = 1;
343              
344 0           my $font;
345             # if the file is already a suitable font object, use it
346 0 0         if (UNIVERSAL::isa($file, 'Font::TTF::Font')) {
347 0           $font = $file;
348             } else {
349 0 0         confess "cannot find font '$file'" unless -f $file;
350 0           $font = Font::TTF::Font->open($file);
351             }
352 0           $data->{'obj'} = $font;
353              
354 0 0         $class = ref $class if ref $class;
355 0           my $self = $class->SUPER::new();
356              
357 0           $self->{'Filter'} = PDFArray(PDFName('FlateDecode'));
358 0           $self->{' font'} = $font;
359 0           $self->{' data'} = $data;
360            
361 0           $data->{'noembed'} = !$opts{'embed'};
362 0 0         $data->{'iscff'} = (defined $font->{'CFF '})? 1: 0;
363              
364 0 0         $self->{'Subtype'} = PDFName('CIDFontType0C') if $data->{'iscff'};
365              
366 0           $data->{'fontfamily'} = $font->{'name'}->read()->find_name(1);
367 0           $data->{'fontname'} = $font->{'name'}->read()->find_name(4);
368              
369 0           $font->{'OS/2'}->read();
370 0           my @stretch = qw[
371             Normal
372             UltraCondensed
373             ExtraCondensed
374             Condensed
375             SemiCondensed
376             Normal
377             SemiExpanded
378             Expanded
379             ExtraExpanded
380             UltraExpanded
381             ];
382 0 0         if (defined $font->{'OS/2'}->{'usWidthClass'}) {
383 0   0       $data->{'fontstretch'} = $stretch[$font->{'OS/2'}->{'usWidthClass'}] || 'Normal';
384             } else {
385 0           $data->{'fontstretch'} = 'Normal';
386             }
387              
388 0           $data->{'fontweight'} = $font->{'OS/2'}->{'usWeightClass'};
389              
390 0 0         if (defined $font->{'OS/2'}->{'sFamilyClass'}) {
391 0           $data->{'panose'} = pack('n', $font->{'OS/2'}->{'sFamilyClass'});
392              
393 0           foreach my $p (qw[bFamilyType bSerifStyle bWeight bProportion bContrast bStrokeVariation bArmStyle bLetterform bMidline bXheight]) {
394 0 0         if (defined $font->{'OS/2'}->{$p}) {
395 0           $data->{'panose'} .= pack('C', $font->{'OS/2'}->{$p});
396             }
397             }
398             }
399              
400 0           $data->{'apiname'} = join('', map { ucfirst(lc(substr($_, 0, 2))) } split m/[^A-Za-z0-9\s]+/, $data->{'fontname'});
  0            
401 0           $data->{'fontname'} =~ s/[\x00-\x1f\s]//og;
402              
403 0           $data->{'altname'} = $font->{'name'}->find_name(1);
404 0           $data->{'altname'} =~ s/[\x00-\x1f\s]//og;
405              
406 0           $data->{'subname'} = $font->{'name'}->find_name(2);
407 0           $data->{'subname'} =~ s/[\x00-\x1f\s]//og;
408              
409             # TBD in PDF::API2 the following line is just find_ms()
410 0   0       $font->{'cmap'}->read()->find_ms($opts{'isocmap'} || 0);
411 0 0         if (defined $font->{'cmap'}->find_ms()) {
412             $data->{'issymbol'} = ($font->{'cmap'}->find_ms()->{'Platform'} == 3 &&
413 0   0       $font->{'cmap'}->read()->find_ms()->{'Encoding'} == 0) || 0;
414             } else {
415 0           $data->{'issymbol'} = 0;
416             }
417              
418 0           $data->{'upem'} = $font->{'head'}->read()->{'unitsPerEm'};
419              
420             $data->{'fontbbox'} = [
421             int($font->{'head'}->{'xMin'} * 1000 / $data->{'upem'}),
422             int($font->{'head'}->{'yMin'} * 1000 / $data->{'upem'}),
423             int($font->{'head'}->{'xMax'} * 1000 / $data->{'upem'}),
424 0           int($font->{'head'}->{'yMax'} * 1000 / $data->{'upem'})
425             ];
426              
427 0           $data->{'stemv'} = 0;
428 0           $data->{'stemh'} = 0;
429              
430 0   0       $data->{'missingwidth'} = int($font->{'hhea'}->read()->{'advanceWidthMax'} * 1000 / $data->{'upem'}) || 1000;
431 0           $data->{'maxwidth'} = int($font->{'hhea'}->{'advanceWidthMax'} * 1000 / $data->{'upem'});
432 0           $data->{'ascender'} = int($font->{'hhea'}->read()->{'Ascender'} * 1000 / $data->{'upem'});
433 0           $data->{'descender'} = int($font->{'hhea'}{'Descender'} * 1000 / $data->{'upem'});
434              
435 0           $data->{'flags'} = 0;
436 0 0         if (defined $font->{'OS/2'}) {
437 0 0         if (defined $font->{'OS/2'}->read()->{'bProportion'}) {
438 0 0         $data->{'flags'} |= 1 if $font->{'OS/2'}->read()->{'bProportion'} == 9;
439             }
440 0 0         if (defined $font->{'OS/2'}{'bSerifStyle'}) {
441             $data->{'flags'} |= 2 unless $font->{'OS/2'}{'bSerifStyle'} > 10 &&
442 0 0 0       $font->{'OS/2'}{'bSerifStyle'} < 14;
443             }
444 0 0         if (defined $font->{'OS/2'}{'bFamilyType'}) {
445 0 0         $data->{'flags'} |= 8 if $font->{'OS/2'}{'bFamilyType'} == 2;
446 0           $data->{'flags'} |= 32; # if $font->{'OS/2'}{'bFamilyType'} > 3;
447             }
448 0 0         if (defined $font->{'OS/2'}{'bLetterform'}) {
449 0 0         $data->{'flags'} |= 64 if $font->{'OS/2'}{'bLetterform'} > 8;
450             }
451             }
452              
453 0   0       $data->{'capheight'} = $font->{'OS/2'}->{'CapHeight'} || int($data->{'fontbbox'}->[3]*0.8);
454 0   0       $data->{'xheight'} = $font->{'OS/2'}->{'xHeight'} || int($data->{'fontbbox'}->[3]*0.4);
455              
456 0 0         if ($data->{'issymbol'}) {
457 0           $data->{'e2u'} = [0xf000 .. 0xf0ff];
458             } else {
459 0           $data->{'e2u'} = [ unpack('U*', decode('cp1252', pack('C*', 0..255))) ];
460             }
461              
462 0 0 0       if ($font->{'post'}->read()->{'FormatType'} == 3 && defined($font->{'cmap'}->read()->find_ms())) {
463 0           $data->{'g2n'} = [];
464 0           foreach my $u (sort {$a <=> $b} keys %{$font->{'cmap'}->read()->find_ms()->{'val'}}) {
  0            
  0            
465 0           my $n = nameByUni($u);
466 0           $data->{'g2n'}->[$font->{'cmap'}->read()->find_ms()->{'val'}->{$u}] = $n;
467             }
468             } else {
469 0 0         $data->{'g2n'} = [ map { $_ || '.notdef' } @{$font->{'post'}->read()->{'VAL'}} ];
  0            
  0            
470             }
471              
472 0           $data->{'italicangle'} = $font->{'post'}->{'italicAngle'};
473 0           $data->{'isfixedpitch'} = $font->{'post'}->{'isFixedPitch'};
474 0           $data->{'underlineposition'} = $font->{'post'}->{'underlinePosition'};
475 0           $data->{'underlinethickness'} = $font->{'post'}->{'underlineThickness'};
476              
477 0 0         if ($self->iscff()) {
478 0           $data->{'cff'} = readcffstructs($font);
479             }
480              
481 0 0         if ($opts{'debug'}) {
482 0           print "CMap determination for file $file\n";
483             }
484 0 0         if ($data->{'issymbol'}) {
485             # force 'find_ms' if we know it's a symbol font anyway
486 0 0         if ($opts{'debug'}) {
487 0           print "This is a symbol font 3/0\n";
488             }
489 0           $opts{'cmaps'} = 'find_ms';
490             }
491              
492             # first, see if CJK .cmap file exists, and want to use it
493             # apparently, very old CJK fonts lack internal cmap tables and need this
494 0           my $CMapfile = '';
495 0 0         if (defined $data->{'cff'}->{'ROS'}) {
496 0           my %cffcmap = (
497             'Adobe:Japan1' => 'japanese',
498             'Adobe:Korea1' => 'korean',
499             'Adobe:CNS1' => 'traditional',
500             'Adobe:GB1' => 'simplified',
501             );
502 0           $CMapfile = $cffcmap{"$data->{'cff'}->{'ROS'}->[0]:$data->{'cff'}->{'ROS'}->[1]"};
503 0 0         if ($opts{'debug'}) {
504 0 0         if ($CMapfile ne '') {
505 0           print "Available CMap file $CMapfile.cmap\n";
506             } else {
507 0           print "No CMap file found\n";
508             }
509             }
510             }
511 0 0         if (!defined $CMapfile) {
512             # CMapfile no longer '', but now undefined!
513 0           $CMapfile = '';
514             }
515              
516 0           my $CMap = $CMapfile; # save original name for later
517 0 0 0       if ($CMapfile ne '' && $opts{'usecmf'}) {
518 0           my $ccmap = _look_for_cmap($CMapfile);
519 0           $data->{'u2g'} = $ccmap->{'u2g'};
520 0           $data->{'g2u'} = $ccmap->{'g2u'};
521             } else {
522             # there is no .cmap file for this alphabet, or we don't want to use it
523 0 0 0       if ($opts{'debug'} && $CMapfile ne '') {
524 0           print "Choose not to use .cmap file\n";
525             }
526 0           $data->{'u2g'} = {};
527              
528 0 0         if ($opts{'debug'}) {
529             # debug stuff
530 0           my $numTables = $font->{'cmap'}{'Num'}; # number of subtables in cmap table
531 0           for my $iii (0 .. $numTables-1) {
532 0           print "CMap Table $iii, ";
533 0           print " Platform/Encoding = ";
534 0           print $font->{'cmap'}{'Tables'}[$iii]{'Platform'};
535 0           print "/";
536 0           print $font->{'cmap'}{'Tables'}[$iii]{'Encoding'};
537 0           print ", Format = ".$font->{'cmap'}{'Tables'}[$iii]{'Format'};
538 0           print ", Ver = ".$font->{'cmap'}{'Tables'}[$iii]{'Ver'};
539 0           print "\n";
540             }
541             }
542              
543             # Platform
544             # 0 = Unicode
545             # 1 = Mac (deprecated)
546             # 2 = ISO (deprecated in favor of Unicode)
547             # 3 = Windows
548             # 4 = Custom
549             # Encodings
550             # Platform 0 (Unicode)
551             # 0 = Unicode 1.0
552             # 1 = Unicode 1.1
553             # 2 = ISO/IEC 10646
554             # 3 = Unicode 2.0+ BMP only, formats 0/4/6
555             # 4 = Unicode 2.0+ full repertoire, formats 0/4/6/10/12
556             # 5 = Unicode Variation Sequences, format 14
557             # 6 = Unicode full repertoire, formats 0/4/6/10/12/13
558             # Platform 1 (Macintosh) has encodings 0-32 for various alphabets
559             # Platform 2 (ISO)
560             # 0 = 7 bit ASCII
561             # 1 = ISO 10646
562             # 2 = ISO 8859-1
563             # Platform 3 (Windows)
564             # 0 = Symbol
565             # 1 = Unicode BMP
566             # 2 = ShiftJIS
567             # 3 = PRC
568             # 4 = Big5
569             # 5 = Wansung
570             # 6 = Johab
571             # 7-9 = Reserved
572             # 10 = Unicode full repertoire
573             # Platform 4 (Custom)
574             # 0-255 OTF Windows NT compatibility mapping
575             # Format 0-14 ?
576             # Ver ?
577              
578 0           my $cmap_list = '';
579 0           my $OS = $^O;
580 0 0         if ($opts{'debug'}) {
581 0           print "OS string is '$OS', ";
582             }
583 0 0 0       if ($OS eq 'MSWin32' || $OS eq 'dos' ||
      0        
      0        
584             $OS eq 'os2' || $OS eq 'cygwin') {
585 0           $OS = 0; # Windows request
586 0 0         if ($opts{'debug'}) {
587 0           print "treat as Windows platform\n";
588             }
589             } else {
590 0           $OS = 1; # non-Windows request
591 0 0         if ($opts{'debug'}) {
592 0           print "treat as non-Windows platform\n";
593             }
594             }
595 0           my $gmap;
596 0 0         if (defined $opts{'cmaps'}) {
597 0           $CMap = $opts{'cmaps'};
598             # 1 or 2 lists, Windows and non-Windows, separated by ;
599             # if no ;, assume same list applies to both Platforms
600             # a list may be the string 'find_ms' to just use that mode
601             # otherwise, a list is p1/e1 p2/e2 etc. separated by max 1 comma
602             # and any number of whitespace
603 0 0         if (index($CMap, ';') < 0) {
604             # no ;, so single entry for both
605 0           $CMap = $CMap.";".$CMap;
606             }
607 0           $cmap_list = (split /;/, $CMap)[$OS];
608 0           $cmap_list =~ s/^\s+//;
609 0           $cmap_list =~ s/\s+$//;
610             } else {
611             # will use @default_CMap list
612 0           $cmap_list = '';
613             }
614 0 0         if ($cmap_list eq '') {
615             # empty list? use default CMap entry
616 0           $cmap_list = $default_CMap[$OS];
617             }
618             # now we have a cmap_list string of target P/E's to look for (either
619             # specified with cmap, or default), OR just 'find_ms'
620 0 0         if ($opts{'debug'}) {
621 0           print "search list '$cmap_list' for match, else find_ms()\n";
622             }
623 0 0         if ($cmap_list eq 'find_ms') {
624             # use original find_ms() call
625 0           $gmap = $font->{'cmap'}->read()->find_ms();
626             } else {
627 0           my @list = split/[,\s]+/, $cmap_list;
628             # should be list of P/E settings, like 0/6, 3/10, etc.
629             # following after code from Bob Hallissy (TTF::Font author)
630 0           my ($cmap, %cmaps, $i);
631 0           $cmap = $font->{'cmap'}->read();
632 0           for ($i = 0; $i < $font->{'cmap'}{'Num'}; $i++) {
633 0           my $s = $font->{'cmap'}{'Tables'}[$i];
634 0           my $key = "$s->{'Platform'}/$s->{'Encoding'}";
635 0           $cmaps{$key} = $s;
636             }
637 0           foreach (@list) {
638 0 0         if ($_ eq '') { next; } # empty entry got into list?
  0            
639 0 0         if (exists $cmaps{$_}) {
640 0           $cmap->{' mstable'} = $cmaps{$_}; # might be unnecessary
641 0 0         if ($opts{'debug'}) {
642 0           print "found internal cmap table '$_' on search list\n";
643             }
644 0           $gmap = $cmaps{$_};
645 0           last;
646             }
647             }
648             } # not 'find_ms' request
649              
650             # final check (.cmap wasn't used). no useful internal cmap found?
651 0 0         if (! $gmap) {
652             # ignored existing .cmap before? use it anyway
653 0 0 0       if ($CMapfile ne '' && !$opts{'usecmf'}) {
654 0 0         if ($opts{'debug'}) {
655 0           print "need to use .cmap file '$CMapfile.cmap' anyway\n";
656             }
657 0           my $ccmap = _look_for_cmap($CMapfile);
658 0           $data->{'u2g'} = $ccmap->{'u2g'};
659 0           $data->{'g2u'} = $ccmap->{'g2u'};
660             } else {
661             # Hail Mary pass to use find_ms()
662 0           $gmap = $font->{'cmap'}->read()->find_ms();
663 0 0         if (! $gmap) {
664 0           die "No useful internal cmap found for $file\n";
665             }
666             }
667             }
668             # we SHOULD have a valid $gmap at this point
669             # load up data->u2g and g2u from gmap (one 'Tables' entry)
670 0           $gmap = $gmap->{'val'};
671 0           foreach my $u (sort {$a<=>$b} keys %{$gmap}) {
  0            
  0            
672 0   0       my $uni = $u || 0;
673 0           $data->{'u2g'}->{$uni} = $gmap->{$uni};
674             }
675 0 0         $data->{'g2u'} = [ map { $_ || 0 } $font->{'cmap'}->read()->reverse() ];
  0            
676             } # no .cmap or don't want to use it
677              
678             # 3/0 cmap table
679 0 0         if ($data->{'issymbol'}) {
680 0   0       map { $data->{'u2g'}->{$_} ||= $font->{'cmap'}->read()->ms_lookup($_) } (0xf000 .. 0xf0ff);
  0            
681 0   0       map { $data->{'u2g'}->{$_ & 0xff} ||= $font->{'cmap'}->read()->ms_lookup($_) } (0xf000 .. 0xf0ff);
  0            
682             }
683              
684 0 0 0       $data->{'e2n'} = [ map { $data->{'g2n'}->[$data->{'u2g'}->{$_} || 0] || '.notdef' } @{$data->{'e2u'}} ];
  0            
  0            
685              
686 0 0 0       $data->{'e2g'} = [ map { $data->{'u2g'}->{$_ || 0} || 0 } @{$data->{'e2u'}} ];
  0            
  0            
687 0           $data->{'u2e'} = {};
688 0           foreach my $n (reverse 0..255) {
689 0 0         $data->{'u2e'}->{$data->{'e2u'}->[$n]} = $n unless defined $data->{'u2e'}->{$data->{'e2u'}->[$n]};
690             }
691              
692 0           $data->{'u2n'} = { map { $data->{'g2u'}->[$_] => $data->{'g2n'}->[$_] } (0 .. (scalar @{$data->{'g2u'}} -1)) };
  0            
  0            
693              
694 0           $data->{'wx'} = [];
695 0           foreach my $i (0..(scalar @{$data->{'g2u'}}-1)) {
  0            
696 0           my $hmtx = $font->{'hmtx'}->read()->{'advance'}->[$i];
697 0 0         if ($hmtx) {
698 0           $data->{'wx'}->[$i] = int($hmtx * 1000/ $data->{'upem'});
699             } else {
700 0           $data->{'wx'}->[$i] = $data->{'missingwidth'};
701             }
702             }
703              
704 0           $data->{'kern'} = read_kern_table($font, $data->{'upem'}, $self);
705 0 0         delete $data->{'kern'} unless defined $data->{'kern'};
706              
707 0           $data->{'fontname'} =~ s/\s+//og;
708 0           $data->{'fontfamily'} =~ s/\s+//og;
709 0           $data->{'apiname'} =~ s/\s+//og;
710 0           $data->{'altname'} =~ s/\s+//og;
711 0           $data->{'subname'} =~ s/\s+//og;
712              
713 0           $self->subsetByCId(0);
714              
715 0           return ($self, $data);
716             } # end of new()
717              
718             sub font {
719 0     0 0   return $_[0]->{' font'};
720             }
721              
722             sub data {
723 0     0 0   return $_[0]->{' data'};
724             }
725              
726             sub iscff {
727 0     0 0   return $_[0]->data()->{'iscff'};
728             }
729              
730             sub haveKernPairs {
731 0 0   0 0   return $_[0]->data()->{'kern'}? 1: 0;
732             }
733              
734             sub kernPairCid {
735 0     0 0   my ($self, $i1, $i2) = @_;
736              
737 0 0 0       return 0 if $i1 == 0 || $i2 == 0;
738 0   0       return $self->data()->{'kern'}->{"$i1:$i2"} || 0;
739             }
740              
741             sub subsetByCId {
742 0     0 0   my $self = shift;
743 0           my $g = shift;
744              
745 0           $self->data()->{'subset'} = 1; # global 'we have subset glyphs'
746 0           vec($self->data()->{'subvec'}, $g, 1) = 1; # this particular glyph into ss
747 0 0         return if $self->iscff();
748             # if loca table not defined in the font (offset into glyf table), is there
749             # an alternative we can use, or just return undef? per Apple TT Ref:
750             # The 'loca' table only used with fonts that have TrueType outlines (that
751             # is, a 'glyf' table). Fonts that have no TrueType outlines do not require
752             # a 'loca' table.
753 0 0         return if !defined $self->font()->{'loca'};
754 0 0         if (defined $self->font()->{'loca'}->read()->{'glyphs'}->[$g]) {
755 0           $self->font()->{'loca'}->read()->{'glyphs'}->[$g]->read();
756 0           return map { vec($self->data()->{'subvec'}, $_, 1) = 1; } $self->font()->{'loca'}->{'glyphs'}->[$g]->get_refs();
  0            
757             }
758 0           return;
759             }
760              
761             sub subvec {
762 0     0 0   my $self = shift;
763 0 0         return 1 if $self->iscff();
764 0           my $g = shift;
765 0           return vec($self->data()->{'subvec'}, $g, 1);
766             }
767              
768             sub glyphNum {
769 0     0 0   my $self = shift;
770 0           return $self->font()->{'maxp'}->read()->{'numGlyphs'};
771             }
772              
773             sub outobjdeep {
774 0     0 1   my ($self, $fh, $pdf) = @_;
775              
776 0           my $f = $self->font();
777              
778 0 0         if ($self->iscff()) {
779 0           $f->{'CFF '}->read_dat();
780             # OTF files were always being written into PDF, even if noembed = 1
781             # TBD: however, API2 omits this check
782 0 0         unless ($self->data()->{'noembed'}) {
783 0           $self->{' stream'} = $f->{'CFF '}->{' dat'};
784             }
785             } else {
786 0 0 0       if ($self->data()->{'subset'} &&
      0        
787             (!defined $self->data()->{'nosubset'} ||
788             !($self->data()->{'nosubset'}) || 0) ) {
789             # glyf table is optional, according to Apple
790 0 0         if (defined $f->{'glyf'}) {
791 0           $f->{'glyf'}->read();
792 0           for (my $i = 0; $i < $self->glyphNum(); $i++) {
793 0 0         next if $self->subvec($i);
794 0           $f->{'loca'}{'glyphs'}->[$i] = undef;
795             }
796             }
797             }
798              
799 0 0         unless ($self->data()->{'noembed'}) {
800 0           $self->{' stream'} = "";
801 0           my $ffh;
802 0           CORE::open($ffh, '+>', \$self->{' stream'});
803 0           binmode($ffh, ':raw');
804 0           $f->out($ffh, 'cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'prep');
805 0           $self->{'Length1'} = PDFNum(length($self->{' stream'}));
806 0           CORE::close($ffh);
807             }
808             }
809              
810 0           return $self->SUPER::outobjdeep($fh, $pdf);
811             }
812              
813             1;