File Coverage

blib/lib/PDF/Builder/Resource/CIDFont/TrueType/FontFile.pm
Criterion Covered Total %
statement 27 459 5.8
branch 0 242 0.0
condition 0 68 0.0
subroutine 9 24 37.5
pod 2 14 14.2
total 38 807 4.7


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