File Coverage

blib/lib/PDF/Builder/Resource/CIDFont/TrueType/FontFile.pm
Criterion Covered Total %
statement 27 473 5.7
branch 0 258 0.0
condition 0 84 0.0
subroutine 9 24 37.5
pod 2 14 14.2
total 38 853 4.4


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