File Coverage

blib/lib/PDF/Builder/Resource/CIDFont.pm
Criterion Covered Total %
statement 38 181 20.9
branch 2 74 2.7
condition 1 80 1.2
subroutine 8 30 26.6
pod 19 23 82.6
total 68 388 17.5


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::CIDFont;
2              
3 2     2   1036 use base 'PDF::Builder::Resource::BaseFont';
  2         4  
  2         1244  
4              
5 2     2   14 use strict;
  2         3  
  2         47  
6 2     2   9 use warnings;
  2         5  
  2         170  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.028'; # manually update whenever code is changed
10              
11 2     2   12 use Encode qw(:all);
  2         4  
  2         569  
12              
13 2     2   12 use PDF::Builder::Basic::PDF::Utils;
  2         4  
  2         176  
14 2     2   12 use PDF::Builder::Util;
  2         4  
  2         6332  
15              
16             =head1 NAME
17              
18             PDF::Builder::Resource::CIDFont - Base class for CID fonts
19              
20             Inherits from L<PDF::Builder::Resource::BaseFont>
21              
22             =head1 METHODS
23              
24             =head2 new
25              
26             $font = PDF::Builder::Resource::CIDFont->new($pdf, $name)
27              
28             =over
29              
30             Returns a cid-font object, base class for all CID-based fonts.
31              
32             =back
33              
34             =cut
35              
36             sub new {
37 1     1 1 5 my ($class, $pdf, $name, %opts) = @_;
38              
39 1 50       5 $class = ref($class) if ref($class);
40 1         17 my $self = $class->SUPER::new($pdf, $name);
41 1 50 33     34 $pdf->new_obj($self) if defined($pdf) && !$self->is_obj($pdf);
42              
43 1         6 $self->{'Type'} = PDFName('Font');
44 1         3 $self->{'Subtype'} = PDFName('Type0');
45 1         6 $self->{'Encoding'} = PDFName('Identity-H');
46              
47 1         4 my $de = PDFDict();
48 1         8 $pdf->new_obj($de);
49 1         5 $self->{'DescendantFonts'} = PDFArray($de);
50              
51 1         4 $de->{'Type'} = PDFName('Font');
52 1         4 $de->{'CIDSystemInfo'} = PDFDict();
53 1         4 $de->{'CIDSystemInfo'}->{'Registry'} = PDFString('Adobe', 'x');
54 1         3 $de->{'CIDSystemInfo'}->{'Ordering'} = PDFString('Identity', 'x');
55 1         5 $de->{'CIDSystemInfo'}->{'Supplement'} = PDFNum(0);
56 1         5 $de->{'CIDToGIDMap'} = PDFName('Identity');
57              
58 1         4 $self->{' de'} = $de;
59              
60 1         5 return $self;
61             }
62              
63             =head2 glyphByCId
64              
65             $n = $font->glyphByCId($gid)
66              
67             =over
68              
69             Returns a character's glyph name (string), given its glyph ID.
70              
71             =back
72              
73             =cut
74              
75             sub glyphByCId {
76 0     0 1 0 my ($self, $gid) = @_;
77 0         0 return $self->data()->{'g2n'}->[$gid];
78             }
79              
80             =head2 uniByCId
81              
82             $u = $font->uniByCId($gid)
83              
84             =over
85              
86             Returns a character's Unicode point, given its glyph ID. If no match, return
87             U+0000.
88              
89             =back
90              
91             =cut
92              
93             sub uniByCId {
94 0     0 1 0 my ($self, $gid) = @_;
95 0         0 my $uni = $self->data()->{'g2u'}->[$gid];
96             # fallback to U+0000 if no match
97 0 0       0 $uni = 0 unless defined $uni;
98 0         0 return $uni;
99             }
100              
101             =head2 cidByUni
102              
103             $c = $font->cidByUni($uid)
104              
105             =over
106              
107             Returns a glyph ID, given its Unicode point.
108              
109             =back
110              
111             =cut
112              
113             # TBD note that cidByUni has been seen returning 'undef' in some cases.
114             # be sure to handle this!
115             sub cidByUni {
116 53     53 1 73 my ($self, $gid) = @_;
117 53         83 return $self->data()->{'u2g'}->{$gid};
118             }
119              
120             =head2 cidByEnc
121              
122             $c = $font->cidByEnc($char)
123              
124             =over
125              
126             Returns a character's glyph ID, given its encoding (single
127             byte value 0 to 255).
128              
129             =back
130              
131             =cut
132              
133             sub cidByEnc {
134 0     0 1   my ($self, $gid) = @_;
135 0           return $self->data()->{'e2g'}->[$gid];
136             }
137              
138             =head2 wxByCId
139              
140             $w = $font->wxByCId($gid)
141              
142             =over
143              
144             Returns a character's width, given its glyph ID.
145             Typically this is based on a 1000 unit wide grid.
146              
147             =back
148              
149             =cut
150              
151             sub wxByCId {
152 0     0 1   my ($self, $g) = @_;
153              
154 0           my $w;
155 0           my $widths = $self->data()->{'wx'};
156              
157 0 0 0       if (ref($widths) eq 'ARRAY' && defined $widths->[$g]) {
    0 0        
158 0           $w = int($widths->[$g]);
159             } elsif (ref($widths) eq 'HASH' && defined $widths->{$g}) {
160 0           $w = int($widths->{$g});
161             } else {
162 0           $w = $self->missingwidth();
163             }
164              
165 0           return $w;
166             }
167              
168             =head2 wxByUni
169              
170             $w = $font->wxByUni($uid)
171              
172             =over
173              
174             Returns a character's width, given its Unicode point.
175             Typically this is based on a 1000 unit wide grid.
176              
177             =back
178              
179             =cut
180              
181             sub wxByUni {
182 0     0 1   my ($self, $gid) = @_;
183 0           return $self->wxByCId($self->data()->{'u2g'}->{$gid});
184             }
185              
186             =head2 wxByEnc
187              
188             $w = $font->wxByEnc($enc)
189              
190             =over
191              
192             Returns a character's width, given its encoding (a single
193             byte character in the range 0 to 255).
194             Typically this is based on a 1000 unit wide grid.
195              
196             =back
197              
198             =cut
199              
200             sub wxByEnc {
201 0     0 1   my ($self, $gid) = @_;
202 0           return $self->wxByCId($self->data()->{'e2g'}->[$gid]);
203             }
204              
205             =head2 width
206              
207             $w = $font->width($string)
208              
209             =over
210              
211             Returns a string's width.
212             This is typically based on a 1000 wide grid for each glyph.
213              
214             =back
215              
216             =cut
217              
218             sub width {
219 0     0 1   my ($self, $text) = @_;
220 0           return $self->width_cid($self->cidsByStr($text));
221             }
222              
223             =head2 width_cid
224              
225             $w = $font->width_cid($gid)
226              
227             =over
228              
229             Returns a character's width, given its glyph ID.
230             This is typically based on a 1000 wide grid for a glyph.
231              
232             =back
233              
234             =cut
235              
236             sub width_cid {
237 0     0 1   my ($self, $text) = @_;
238              
239 0           my $width = 0;
240 0           my $lastglyph = 0;
241 0           foreach my $n (unpack('n*', $text)) {
242 0           $width += $self->wxByCId($n);
243 0 0 0       if ($self->{'-dokern'} && $self->haveKernPairs()) {
244 0 0         if ($self->kernPairCid($lastglyph, $n)) {
245 0           $width -= $self->kernPairCid($lastglyph, $n);
246             }
247             }
248 0           $lastglyph = $n;
249             }
250 0           $width /= 1000;
251 0           return $width;
252             }
253              
254             =head2 cidsByStr
255              
256             $cidstring = $font->cidsByStr($string)
257              
258             =over
259              
260             Returns the cid-string (as a single text string, not an array) from string,
261             based on the font's encoding map.
262              
263             =back
264              
265             =cut
266              
267             sub _cidsByStr {
268 0     0     my ($self, $s) = @_;
269              
270 0           $s = pack('n*', map { $self->cidByEnc($_) } unpack('C*', $s));
  0            
271 0           return $s;
272             }
273              
274             sub cidsByStr {
275 0     0 1   my ($self, $text) = @_;
276              
277 0 0 0       if (utf8::is_utf8($text) &&
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
278             defined $self->data()->{'decode'} &&
279             $self->data()->{'decode'} ne 'ident') {
280 0           $text = encode($self->data()->{'decode'}, $text);
281             } elsif (utf8::is_utf8($text) &&
282             defined $self->data()->{'decode'} &&
283             $self->data()->{'decode'} eq 'ident') {
284 0           $text = $self->cidsByUtf($text);
285             } elsif (!utf8::is_utf8($text) &&
286             defined $self->data()->{'encode'} &&
287             defined $self->data()->{'decode'} &&
288             $self->data()->{'decode'} eq 'ident') {
289 0           $text = $self->cidsByUtf(decode($self->data()->{'encode'}, $text));
290             } elsif (!utf8::is_utf8($text) &&
291             $self->can('issymbol') &&
292             $self->issymbol() &&
293             defined $self->data()->{'decode'} &&
294             $self->data()->{'decode'} eq 'ident') {
295 0           $text = pack('U*', (map { $_+0xf000 } unpack('C*', $text)));
  0            
296 0           $text = $self->cidsByUtf($text);
297             } else {
298 0           $text = $self->_cidsByStr($text);
299             }
300 0           return $text;
301             }
302              
303             =head2 cidsByUtf
304              
305             $cidstring = $font->cidsByUtf($utf8string)
306              
307             =over
308              
309             Returns the CID-encoded string (a text string, not an array) from utf8-string.
310              
311             =back
312              
313             =cut
314              
315             sub cidsByUtf {
316 0     0 1   my ($self, $s) = @_;
317              
318             $s = pack('n*',
319 0 0         map { $self->cidByUni($_)||0 }
320             (map {
321 0 0 0       ($_ and $_>0x7f and $_<0xA0)? uniByName(nameByUni($_)): $_
  0            
322             }
323             unpack('U*', $s)));
324              
325 0           utf8::downgrade($s);
326 0           return $s;
327             }
328              
329             =head2 textByStr
330              
331             $cid_string = $font->textByStr($text)
332              
333             =over
334              
335             Returns a cid text string, given a text string.
336              
337             =back
338              
339             =cut
340              
341             sub textByStr {
342 0     0 1   my ($self, $text) = @_;
343 0           return $self->text_cid($self->cidsByStr($text));
344             }
345              
346             =head2 textByStrKern
347              
348             $cid_string = $font->textByStrKern($text, $size, $indent)
349              
350             =over
351              
352             Returns a cid string, given character text, size, and indentation.
353              
354             =back
355              
356             =cut
357              
358             sub textByStrKern {
359 0     0 1   my ($self, $text, $size, $indent) = @_;
360 0           return $self->text_cid_kern($self->cidsByStr($text), $size, $indent);
361             }
362              
363             =head2 text
364              
365             $stream_str = $font->text($text, $size, $indent)
366              
367             =over
368              
369             Returns a PDF text stream-ready code block to output the given text.
370              
371             =back
372              
373             =cut
374              
375             sub text {
376 0     0 1   my ($self, $text, $size, $indent) = @_;
377              
378             # need to break up $text into fragments ending with x20
379             # TBD: handle other spaces (espec. xA0) "appropriately" (control by flag)
380             # 0 = x20 space only
381             # 1 (default) = x20 and same/longer spaces
382             # 2 = all spaces
383             # the problem is, other font types handle only x20 in Reader
384 0           my ($latest_page, $wordspace, $fontsize);
385 0           $latest_page = $self->{' apipdf'}->{' outlist'}[0]->{'Pages'}->{'Kids'}->{' val'}[-1];
386 0           $wordspace = $latest_page->{'Contents'}->{' val'}->[1]->{' wordspace'};
387 0           $fontsize = $latest_page->{'Contents'}->{' val'}->[1]->{' fontsize'};
388 0 0 0       if (!defined $wordspace || !defined $fontsize || $fontsize <= 0) {
      0        
389 0           $wordspace = $latest_page->{'Contents'}->{' val'}->[0]->{' wordspace'};
390 0           $fontsize = $latest_page->{'Contents'}->{' val'}->[0]->{' fontsize'};
391             }
392 0           my @fragments = ( $text ); # default for wordspace = 0
393             # TBD: get list of different lengths of spaces found, split on all of them
394             # could have null fragments where two or more spaces in a row, or
395             # text ended with a space
396 0 0         if ($wordspace) {
397             # split appears to drop trailing blanks, so need a guard
398 0           @fragments = split / /, $text."|";
399 0           chop($fragments[-1]);
400             }
401              
402 0           my $out_str = '';
403 0           for (my $i = 0; $i <= $#fragments; $i++) {
404 0 0         if ($fragments[$i] ne '') {
405 0           my $newtext = $self->textByStr($fragments[$i]); # '<glyphIDsList>'
406 0 0 0       if (defined $size && $self->{'-dokern'}) {
    0          
407 0           $newtext = $self->textByStrKern($fragments[$i], $size, $indent);
408 0           $out_str .= $newtext;
409             } elsif (defined $size) {
410 0 0 0       if (defined($indent) && $indent!=0) {
411 0           $out_str .= "[ $indent $newtext ] TJ";
412             } else {
413 0           $out_str .= "$newtext Tj";
414             }
415             } else {
416 0           $out_str .= $newtext;
417             }
418             }
419             # unless this is the last fragment (no space follows), add a "kerned"
420             # space to out_str (reduce its effective width by moving left).
421             # TBD: different spaces of different lengths with different "kerns"
422 0 0         if ($i < $#fragments) {
423 0           $out_str .= "[ ".$self->textByStrKern(' ')." ".(-$wordspace/$fontsize*1000)." ] TJ";
424             }
425             }
426 0           return $out_str;
427             }
428              
429             =head2 text_cid
430              
431             $stream_str = $font->text_cid($text, $size)
432              
433             =over
434              
435             Returns a PDF text stream-ready output using glyph IDs, given input text and
436             size.
437              
438             =back
439              
440             =cut
441              
442             sub text_cid {
443 0     0 1   my ($self, $text, $size) = @_;
444              
445 0 0         if ($self->can('fontfile')) {
446 0           foreach my $g (unpack('n*', $text)) {
447 0           $self->fontfile()->subsetByCId($g);
448             }
449             }
450 0           my $newtext = unpack('H*', $text);
451 0 0         if (defined $size) {
452 0           return "<$newtext> Tj";
453             } else {
454 0           return "<$newtext>";
455             }
456             }
457              
458             =head2 text_cid_kern
459              
460             $font->text_cid_kern($text, $size, $indent)
461              
462             =over
463              
464             Returns a PDF output-ready stream command using glyph IDs, given text, size,
465             and indentation.
466              
467             =back
468              
469             =cut
470              
471             sub text_cid_kern {
472 0     0 1   my ($self, $text, $size, $indent) = @_;
473              
474 0 0         if ($self->can('fontfile')) {
475 0           foreach my $g (unpack('n*', $text)) {
476 0           $self->fontfile()->subsetByCId($g);
477             }
478             }
479 0 0 0       if (defined $size && $self->{'-dokern'} && $self->haveKernPairs()) {
    0 0        
480 0           my $newtext = ' ';
481 0           my $lastglyph = 0;
482 0           my $tBefore = 0;
483 0           foreach my $n (unpack('n*', $text)) {
484 0 0         if ($self->kernPairCid($lastglyph, $n)) {
485 0 0         $newtext .= '> ' if $tBefore;
486 0           $newtext .= sprintf('%i ', $self->kernPairCid($lastglyph, $n));
487 0           $tBefore = 0;
488             }
489 0           $lastglyph = $n;
490 0           my $t = sprintf('%04X', $n);
491 0 0         $newtext .= '<' unless $tBefore;
492 0           $newtext .= $t;
493 0           $tBefore = 1;
494             }
495 0 0         $newtext .= '> ' if $tBefore;
496 0 0 0       if (defined($indent) && $indent != 0) {
497 0           return "[ $indent $newtext ] TJ";
498             } else {
499 0           return "[ $newtext ] TJ";
500             }
501             } elsif (defined $size) {
502 0           my $newtext = unpack('H*', $text);
503 0 0 0       if (defined($indent) && $indent != 0) {
504 0           return "[ $indent <$newtext> ] TJ";
505             } else {
506 0           return "<$newtext> Tj";
507             }
508             } else {
509 0           my $newtext = unpack('H*', $text);
510 0           return "<$newtext>";
511             }
512             }
513              
514             sub kernPairCid {
515 0     0 0   return 0;
516             }
517              
518             sub haveKernPairs {
519 0     0 0   return 0; # PDF::API2 changed to just 'return;'
520             }
521              
522             =head2 encodeByName
523              
524             $font = $font->encodeByName($enc)
525              
526             =over
527              
528             Returns updated $font object, given an input encoding.
529              
530             =back
531              
532             =cut
533              
534             sub encodeByName {
535 0     0 1   my ($self, $enc) = @_;
536              
537 0 0         return if $self->issymbol();
538              
539 0 0         if (defined $enc) {
540             $self->data()->{'e2u'} = [
541 0 0 0       map { ($_ and $_>0x7f and $_<0xA0)? uniByName(nameByUni($_)): $_ }
  0            
542             unpack('U*', decode($enc, pack('C*', 0..255)))
543             ];
544             }
545             $self->data()->{'e2n'} = [
546 0 0 0       map { $self->data()->{'g2n'}->[$self->data()->{'u2g'}->{$_} || 0] || '.notdef' }
547 0           @{$self->data()->{'e2u'}}
  0            
548             ];
549             $self->data()->{'e2g'} = [
550 0 0         map { $self->data()->{'u2g'}->{$_} || 0 }
551 0           @{$self->data()->{'e2u'}}
  0            
552             ];
553              
554 0           $self->data()->{'u2e'} = {};
555 0           foreach my $n (reverse 0..255) {
556 0   0       $self->data()->{'u2e'}->{$self->data()->{'e2u'}->[$n]} //= $n;
557             }
558              
559 0           return $self;
560             }
561              
562             sub subsetByCId {
563 0     0 0   return 1;
564             }
565              
566             sub subvec {
567 0     0 0   return 1;
568             }
569              
570             =head2 glyphNum
571              
572             $g_or_w = $font->glyphNum()
573              
574             =over
575              
576             If 'glyphs' table is defined for a font, return its size;
577             otherwise, return the 'wx' widths table size.
578              
579             =back
580              
581             =cut
582              
583             sub glyphNum {
584 0     0 1   my $self = shift;
585              
586 0 0         if (defined $self->data()->{'glyphs'}) {
587 0           return $self->data()->{'glyphs'};
588             }
589 0           return scalar @{$self->data()->{'wx'}};
  0            
590             }
591              
592             #sub outobjdeep {
593             # my ($self, $fh, $pdf, %opts) = @_;
594             #
595             # return $self->SUPER::outobjdeep($fh, $pdf, %opts);
596             #}
597              
598             1;