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