File Coverage

blib/lib/PDF/Builder/Resource/BaseFont.pm
Criterion Covered Total %
statement 165 255 64.7
branch 32 78 41.0
condition 32 77 41.5
subroutine 36 58 62.0
pod 46 50 92.0
total 311 518 60.0


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::BaseFont;
2              
3 9     9   2219 use base 'PDF::Builder::Resource';
  9         19  
  9         1056  
4              
5 9     9   60 use strict;
  9         20  
  9         205  
6 9     9   41 use warnings;
  9         22  
  9         597  
7              
8             our $VERSION = '3.023'; # VERSION
9             our $LAST_UPDATE = '3.018'; # manually update whenever code is changed
10              
11 9     9   68 use Compress::Zlib;
  9         18  
  9         3125  
12 9     9   70 use Encode qw(:all);
  9         20  
  9         2533  
13 9     9   96 use PDF::Builder::Basic::PDF::Utils;
  9         22  
  9         870  
14 9     9   157 use PDF::Builder::Util;
  9         25  
  9         1360  
15 9     9   64 use Scalar::Util qw(weaken);
  9         30  
  9         36633  
16              
17             =head1 NAME
18              
19             PDF::Builder::Resource::BaseFont - Base class for font resources
20              
21             =head1 METHODS
22              
23             =over
24              
25             =item $font = PDF::Builder::Resource::BaseFont->new($pdf, $name)
26              
27             Return a font resource object.
28              
29             =cut
30              
31             sub new {
32 53     53 1 235 my ($class, $pdf, $name) = @_;
33              
34 53         149 my $self;
35              
36 53 50       253 $class = ref($class) if ref($class);
37 53         460 $self = $class->SUPER::new($pdf, $name);
38              
39 53 50       226 $pdf->new_obj($self) unless $self->is_obj($pdf);
40              
41 53         234 $self->{'Type'} = PDFName('Font');
42              
43 53         179 $self->{' apipdf'} = $pdf;
44 53         240 weaken $self->{' apipdf'};
45              
46 53         174 return $self;
47             }
48              
49             sub data {
50 40301     40301 0 102476 return $_[0]->{' data'};
51             }
52              
53             =item $descriptor = $font->descrByData()
54              
55             Return the font's FontDescriptor key structure based on the font's data.
56              
57             =cut
58              
59             sub descrByData {
60 16     16 1 38 my $self = shift();
61              
62 16         61 my $descriptor = PDFDict();
63 16         83 $self->{' apipdf'}->new_obj($descriptor);
64              
65 16         44 $descriptor->{'Type'} = PDFName('FontDescriptor');
66 16         66 $descriptor->{'FontName'} = PDFName($self->fontname());
67              
68 16   100     80 my @box = map { PDFNum($_ || 0) } $self->fontbbox();
  64         206  
69 16         64 $descriptor->{'FontBBox'} = PDFArray(@box);
70              
71 16   50     83 $descriptor->{'Ascent'} = PDFNum($self->ascender() || 0);
72 16   50     79 $descriptor->{'Descent'} = PDFNum($self->descender() || 0);
73 16   100     91 $descriptor->{'ItalicAngle'} = PDFNum($self->italicangle() || 0.0);
74 16   50     86 $descriptor->{'XHeight'} = PDFNum($self->xheight() || (($self->fontbbox())[3]*0.5) || 500);
75 16   50     79 $descriptor->{'CapHeight'} = PDFNum($self->capheight() || ($self->fontbbox())[3] || 800);
76 16   50     83 $descriptor->{'StemV'} = PDFNum($self->stemv() || 0);
77 16   50     75 $descriptor->{'StemH'} = PDFNum($self->stemh() || 0);
78 16   50     75 $descriptor->{'AvgWidth'} = PDFNum($self->avgwidth() || 300);
79 16   100     81 $descriptor->{'MissingWidth'} = PDFNum($self->missingwidth() || 300);
80 16   66     65 $descriptor->{'MaxWidth'} = PDFNum($self->maxwidth() || $self->missingwidth() || ($self->fontbbox())[2]);
81 16 50 50     51 $descriptor->{'Flags'} = PDFNum($self->flags() || 0) unless $self->data()->{'iscore'};
82 16 100       59 if (defined $self->data()->{'panose'}) {
83 1         6 $descriptor->{'Style'} = PDFDict();
84 1         5 $descriptor->{'Style'}->{'Panose'} = PDFStrHex($self->data()->{'panose'});
85             }
86             $descriptor->{'FontFamily'} = PDFString($self->data()->{'fontfamily'}, 'x')
87 16 100       45 if defined $self->data()->{'fontfamily'};
88             $descriptor->{'FontWeight'} = PDFNum($self->data()->{'fontweight'})
89 16 100       47 if defined $self->data()->{'fontweight'};
90             $descriptor->{'FontStretch'} = PDFName($self->data()->{'fontstretch'})
91 16 100       40 if defined $self->data()->{'fontstretch'};
92              
93 16         78 return $descriptor;
94             }
95              
96             sub tounicodemap {
97 1     1 0 2 my $self = shift();
98              
99 1 50       4 return $self if defined $self->{'ToUnicode'};
100              
101 1         2 my $stream = qq|\%\% Custom\n\%\% CMap\n\%\%\n/CIDInit /ProcSet findresource begin\n|;
102 1         3 $stream .= qq|12 dict begin begincmap\n|;
103 1         3 $stream .= qq|/CIDSystemInfo <<\n|;
104 1         6 $stream .= sprintf(qq| /Registry (%s)\n|, $self->name());
105 1         3 $stream .= qq| /Ordering (XYZ)\n|;
106 1         2 $stream .= qq| /Supplement 0\n|;
107 1         3 $stream .= qq|>> def\n|;
108 1         13 $stream .= sprintf(qq|/CMapName /pdfbldr-%s+0 def\n|, $self->name());
109 1 50 33     21 if ($self->can('uniByCId') and $self->can('glyphNum')) {
110             # this is a type0 font
111 0         0 $stream .= sprintf(qq|1 begincodespacerange <0000> <%04X> endcodespacerange\n|, $self->glyphNum() - 1);
112 0         0 for (my $j = 0; $j < $self->glyphNum(); $j++) {
113 0 0       0 my $i = $self->glyphNum() - $j > 100 ? 100 : $self->glyphNum() - $j;
114 0 0       0 if ($j == 0) {
    0          
115 0         0 $stream .= qq|$i beginbfrange\n|;
116             } elsif ($j % 100 == 0) {
117 0         0 $stream .= qq|endbfrange\n|;
118 0         0 $stream .= qq|$i beginbfrange\n|;
119             }
120             # Default to 0000 if uniByCId returns undef in order to match
121             # previous behavior minus an uninitialized value warning. It's
122             # worth looking into what should be happening here, since this may
123             # not be the correct behavior.
124 0   0     0 $stream .= sprintf(qq|<%04x> <%04x> <%04x>\n|, $j, $j, ($self->uniByCId($j) // 0));
125             }
126 0         0 $stream .= "endbfrange\n";
127             } else {
128             # everything else is single byte font
129 1         3 $stream .= qq|1 begincodespacerange\n<00> \nendcodespacerange\n|;
130 1         3 $stream .= qq|256 beginbfchar\n|;
131 1         5 for (my $j=0; $j < 256; $j++) {
132 256         455 $stream .= sprintf(qq|<%02X> <%04X>\n|, $j, $self->uniByEnc($j));
133             }
134 1         7 $stream .= qq|endbfchar\n|;
135             }
136 1         2 $stream .= qq|endcmap CMapName currendict /CMap defineresource pop end end\n|;
137              
138 1         4 my $cmap = PDFDict();
139 1         4 $cmap->{'Type'} = PDFName('CMap');
140 1         5 $cmap->{'CMapName'} = PDFName(sprintf(qq|pdfbldr-%s+0|, $self->name()));
141 1         3 $cmap->{'CIDSystemInfo'} = PDFDict();
142 1         3 $cmap->{'CIDSystemInfo'}->{'Registry'} = PDFString($self->name(), 'x');
143 1         14 $cmap->{'CIDSystemInfo'}->{'Ordering'} = PDFString('XYZ', 'x');
144 1         4 $cmap->{'CIDSystemInfo'}->{'Supplement'} = PDFNum(0);
145              
146 1         5 $self->{' apipdf'}->new_obj($cmap);
147 1         2 $cmap->{' nofilt'} = 1;
148 1         6 $cmap->{' stream'} = Compress::Zlib::compress($stream);
149 1         807 $cmap->{'Filter'} = PDFArray(PDFName('FlateDecode'));
150 1         4 $self->{'ToUnicode'} = $cmap;
151              
152 1         3 return $self;
153             }
154              
155             =back
156              
157             =head1 FONT-MANAGEMENT RELATED METHODS
158              
159             =over
160              
161             =item $name = $font->fontname()
162              
163             Return the font's name (a.k.a. display name).
164              
165             =cut
166              
167             sub fontname {
168 70     70 1 223 return $_[0]->data()->{'fontname'};
169             }
170              
171             =item $name = $font->altname()
172              
173             Return the font's alternative name (a.k.a. Windows name for a PostScript font).
174              
175             =cut
176              
177             sub altname {
178 0     0 1 0 return $_[0]->data()->{'altname'};
179             }
180              
181             =item $name = $font->subname()
182              
183             Return the font's subname (a.k.a. font variant).
184              
185             =cut
186              
187             sub subname {
188 0     0 1 0 return $_[0]->data()->{'subname'};
189             }
190              
191             =item $name = $font->apiname()
192              
193             Return the font's name to be used internally (should be equal to $font->name()).
194              
195             =cut
196              
197             sub apiname {
198 0     0 1 0 return $_[0]->data()->{'apiname'};
199             }
200              
201             =item $issymbol = $font->issymbol()
202              
203             Return the font's symbol flag (i.e., is this a symbol font).
204              
205             =cut
206              
207             sub issymbol {
208 52     52 1 169 return $_[0]->data()->{'issymbol'};
209             }
210              
211             =item $iscff = $font->iscff()
212              
213             Return the font's Compact Font Format flag.
214              
215             =cut
216              
217             sub iscff {
218 0     0 1 0 return $_[0]->data()->{'iscff'};
219             }
220              
221             =back
222              
223             =head1 TYPOGRAPHY-RELATED METHODS
224              
225             =over
226              
227             =item ($llx,$lly, $urx,$ury) = $font->fontbbox()
228              
229             Return the font's bounding box.
230              
231             =cut
232              
233             sub fontbbox {
234 31     31 1 60 my @bbox = @{$_[0]->data()->{'fontbbox'}};
  31         89  
235             # rearrange to LL UR order
236 31 50       93 if ($bbox[0] > $bbox[2]) {
237 0         0 @bbox = ($bbox[2], $bbox[3], $bbox[0], $bbox[1]);
238             }
239 31         138 return @bbox;
240             }
241              
242             =item $capheight = $font->capheight()
243              
244             Return the font's capheight value.
245              
246             =cut
247              
248             sub capheight {
249 16     16 1 59 return $_[0]->data()->{'capheight'};
250             }
251              
252             =item $xheight = $font->xheight()
253              
254             Return the font's xheight value.
255              
256             =cut
257              
258             sub xheight {
259 16     16 1 67 return $_[0]->data()->{'xheight'};
260             }
261              
262             =item $missingwidth = $font->missingwidth()
263              
264             Return the font's missingwidth value.
265              
266             =cut
267              
268             sub missingwidth {
269 231     231 1 449 return $_[0]->data()->{'missingwidth'};
270             }
271              
272             =item $maxwidth = $font->maxwidth()
273              
274             Return the font's maxwidth value.
275              
276             =cut
277              
278             sub maxwidth {
279 16     16 1 51 return $_[0]->data()->{'maxwidth'};
280             }
281              
282             =item $avgwidth = $font->avgwidth()
283              
284             Return the font's avgwidth (average width) value.
285              
286             =cut
287              
288             sub avgwidth {
289 16     16 1 48 my ($self) = @_;
290              
291 16         62 my $aw = $self->data()->{'avgwidth'};
292 16   33     135 $aw ||= ((
293             # numbers are character-frequency weighting counts
294             # presumably for English text... ? it may be a little off for
295             # other languages
296             $self->wxByGlyph('a')*64 +
297             $self->wxByGlyph('b')*14 +
298             $self->wxByGlyph('c')*27 +
299             $self->wxByGlyph('d')*35 +
300             $self->wxByGlyph('e')*100 +
301             $self->wxByGlyph('f')*20 +
302             $self->wxByGlyph('g')*14 +
303             $self->wxByGlyph('h')*42 +
304             $self->wxByGlyph('i')*63 +
305             $self->wxByGlyph('j')* 3 +
306             $self->wxByGlyph('k')* 6 +
307             $self->wxByGlyph('l')*35 +
308             $self->wxByGlyph('m')*20 +
309             $self->wxByGlyph('n')*56 +
310             $self->wxByGlyph('o')*56 +
311             $self->wxByGlyph('p')*17 +
312             $self->wxByGlyph('q')* 4 +
313             $self->wxByGlyph('r')*49 +
314             $self->wxByGlyph('s')*56 +
315             $self->wxByGlyph('t')*71 +
316             $self->wxByGlyph('u')*31 +
317             $self->wxByGlyph('v')*10 +
318             $self->wxByGlyph('w')*18 +
319             $self->wxByGlyph('x')* 3 +
320             $self->wxByGlyph('y')*18 +
321             $self->wxByGlyph('z')* 2 +
322             $self->wxByGlyph('A')*64 +
323             $self->wxByGlyph('B')*14 +
324             $self->wxByGlyph('C')*27 +
325             $self->wxByGlyph('D')*35 +
326             $self->wxByGlyph('E')*100 +
327             $self->wxByGlyph('F')*20 +
328             $self->wxByGlyph('G')*14 +
329             $self->wxByGlyph('H')*42 +
330             $self->wxByGlyph('I')*63 +
331             $self->wxByGlyph('J')* 3 +
332             $self->wxByGlyph('K')* 6 +
333             $self->wxByGlyph('L')*35 +
334             $self->wxByGlyph('M')*20 +
335             $self->wxByGlyph('N')*56 +
336             $self->wxByGlyph('O')*56 +
337             $self->wxByGlyph('P')*17 +
338             $self->wxByGlyph('Q')* 4 +
339             $self->wxByGlyph('R')*49 +
340             $self->wxByGlyph('S')*56 +
341             $self->wxByGlyph('T')*71 +
342             $self->wxByGlyph('U')*31 +
343             $self->wxByGlyph('V')*10 +
344             $self->wxByGlyph('W')*18 +
345             $self->wxByGlyph('X')* 3 +
346             $self->wxByGlyph('Y')*18 +
347             $self->wxByGlyph('Z')* 2 +
348             $self->wxByGlyph('space')*332
349             ) / 2000);
350              
351 16         143 return int($aw);
352             }
353              
354             =item $flags = $font->flags()
355              
356             Return the font's flags value.
357              
358             =cut
359              
360             sub flags {
361 16     16 1 45 return $_[0]->data()->{'flags'};
362             }
363              
364             =item $stemv = $font->stemv()
365              
366             Return the font's stemv value.
367              
368             =cut
369              
370             sub stemv {
371 16     16 1 58 return $_[0]->data()->{'stemv'};
372             }
373              
374             =item $stemh = $font->stemh()
375              
376             Return the font's stemh value.
377              
378             =cut
379              
380             sub stemh {
381 16     16 1 66 return $_[0]->data()->{'stemh'};
382             }
383              
384             =item $italicangle = $font->italicangle()
385              
386             Return the font's italicangle value.
387              
388             =cut
389              
390             sub italicangle {
391 16     16 1 58 return $_[0]->data()->{'italicangle'};
392             }
393              
394             =item $isfixedpitch = $font->isfixedpitch()
395              
396             Return the font's isfixedpitch flag.
397              
398             =cut
399              
400             sub isfixedpitch {
401 0     0 1 0 return $_[0]->data()->{'isfixedpitch'};
402             }
403              
404             =item $underlineposition = $font->underlineposition()
405              
406             Return the font's underlineposition value.
407              
408             =cut
409              
410             sub underlineposition {
411 0     0 1 0 return $_[0]->data()->{'underlineposition'};
412             }
413              
414             =item $underlinethickness = $font->underlinethickness()
415              
416             Return the font's underlinethickness value.
417              
418             =cut
419              
420             sub underlinethickness {
421 0     0 1 0 return $_[0]->data()->{'underlinethickness'};
422             }
423              
424             =item $ascender = $font->ascender()
425              
426             Return the font's ascender value.
427              
428             =cut
429              
430             sub ascender {
431 16     16 1 54 return $_[0]->data()->{'ascender'};
432             }
433              
434             =item $descender = $font->descender()
435              
436             Return the font's descender value.
437              
438             =cut
439              
440             sub descender {
441 16     16 1 54 return $_[0]->data()->{'descender'};
442             }
443              
444             =back
445              
446             =head1 GLYPH-RELATED METHODS
447              
448             =over 4
449              
450             =item @names = $font->glyphNames()
451              
452             Return the defined glyph names of the font.
453              
454             =cut
455              
456             sub glyphNames {
457 0     0 1 0 return keys %{$_[0]->data()->{'wx'}};
  0         0  
458             }
459              
460             =item $glNum = $font->glyphNum()
461              
462             Return the number of defined glyph names of the font.
463              
464             =cut
465              
466             sub glyphNum {
467             #my $self = shift();
468             #return scalar $self->glyphNames();
469 0     0 1 0 return scalar keys %{$_[0]->data()->{'wx'}};
  0         0  
470             }
471              
472             =item $uni = $font->uniByGlyph($char)
473              
474             Return the unicode by glyph name.
475              
476             =cut
477              
478             sub uniByGlyph {
479             #my ($self, $name) = @_;
480             #return $self->data()->{'n2u'}->{$name};
481 0     0 1 0 return $_[0]->data()->{'n2u'}->{$_[1]};
482             }
483              
484             =item $uni = $font->uniByEnc($char)
485              
486             Return the Unicode by the font's encoding map.
487              
488             =cut
489              
490             sub uniByEnc {
491 256     256 1 388 my ($self, $enc) = @_;
492 256         379 my $uni = $self->data()->{'e2u'}->[$enc];
493             # fallback to U+0000 if no match
494 256 50       457 $uni = 0 unless defined $uni;
495 256         637 return $uni;
496             }
497              
498             =item $uni = $font->uniByMap($char)
499              
500             Return the Unicode by the font's default map.
501              
502             =cut
503              
504             sub uniByMap {
505 0     0 1 0 return $_[0]->data()->{'uni'}->[$_[1]];
506             }
507              
508             =item $char = $font->encByGlyph($glyph)
509              
510             Return the character by the given glyph name of the font's encoding map.
511              
512             =cut
513              
514             sub encByGlyph {
515 0   0 0 1 0 return $_[0]->data()->{'n2e'}->{$_[1]} || 0;
516             }
517              
518             =item $char = $font->encByUni($uni)
519              
520             Return the character by the given Unicode of the font's encoding map.
521              
522             =cut
523              
524             sub encByUni {
525             return $_[0]->data()->{'u2e'}->{$_[1]} ||
526 27   50 27 1 54 $_[0]->data()->{'u2c'}->{$_[1]} ||
527             0;
528             }
529              
530             =item $char = $font->mapByGlyph($glyph)
531              
532             Return the character by the given glyph name of the font's default map.
533              
534             =cut
535              
536             sub mapByGlyph {
537 0   0 0 1 0 return $_[0]->data()->{'n2c'}->{$_[1]} || 0;
538             }
539              
540             =item $char = $font->mapByUni($uni)
541              
542             Return the character by the given Unicode of the font's default map.
543              
544             =cut
545              
546             sub mapByUni {
547 0   0 0 1 0 return $_[0]->data()->{'u2c'}->{$_[1]} || 0;
548             }
549              
550             =item $name = $font->glyphByUni($unicode)
551              
552             Return the glyph's name by the font's Unicode map.
553             B non-standard glyph-names are mapped onto
554             the ms-symbol area (0xF000).
555              
556             =cut
557              
558             sub glyphByUni {
559 0   0 0 1 0 return $_[0]->data()->{'u2n'}->{$_[1]} || '.notdef';
560             }
561              
562             =item $name = $font->glyphByEnc($char)
563              
564             Return the glyph's name by the font's encoding map.
565              
566             =cut
567              
568             sub glyphByEnc {
569 25398     25398 1 42270 return $_[0]->data()->{'e2n'}->[$_[1]];
570             }
571              
572             =item $name = $font->glyphByMap($char)
573              
574             Return the glyph's name by the font's default map.
575              
576             =cut
577              
578             sub glyphByMap {
579 0     0 1 0 return $_[0]->data()->{'char'}->[$_[1]];
580             }
581              
582             =item $width = $font->wxByGlyph($glyph)
583              
584             Return the glyph's width.
585             This is a value, that when divided by 1000 and multiplied by
586             the font size (height in points), gives the advance width to the
587             next character's start. Typically, the width will be under 1000.
588              
589             =cut
590              
591             sub wxByGlyph {
592 848     848 1 1375 my ($self, $glyph) = @_;
593              
594 848         1117 my $width;
595 848 100       1235 if (ref($self->data()->{'wx'}) eq 'HASH') {
596 795 50       1625 $width = $self->data()->{'wx'}->{$glyph} if defined $glyph;
597             } else {
598 53         106 my $cid = $self->cidByUni(uniByName($glyph));
599 53 50       120 $width = $self->data()->{'wx'}->[$cid] if defined $cid;
600             }
601 848   66     1587 $width //= $self->missingwidth();
602 848   100     1514 $width //= 300;
603              
604 848         2407 return $width;
605             }
606              
607             =item $width = $font->wxByUni($uni)
608              
609             Return the Unicode character's width.
610             This is a value, that when divided by 1000 and multiplied by
611             the font size (height in points), gives the advance width to the
612             next character's start. Typically, the width will be under 1000.
613              
614             =cut
615              
616             sub wxByUni {
617 0     0 1 0 my ($self, $uni) = @_;
618 0         0 my ($gid, $width);
619              
620 0 0       0 $gid = $self->glyphByUni($uni) if defined $uni;
621 0 0       0 $width = $self->data()->{'wx'}->{$gid} if defined $gid;
622 0   0     0 $width //= $self->missingwidth();
623 0   0     0 $width //= 300;
624              
625 0         0 return $width;
626             }
627              
628             =item $width = $font->wxByEnc($char)
629              
630             Return the character's width based on the current encoding.
631             This is a value, that when divided by 1000 and multiplied by
632             the font size (height in points), gives the advance width to the
633             next character's start. Typically, the width will be under 1000.
634              
635             =cut
636              
637             sub wxByEnc {
638 12086     12086 1 18839 my ($self, $char) = @_;
639              
640 12086         15786 my $glyph;
641 12086 50       25305 $glyph = $self->glyphByEnc($char) if defined $char;
642 12086         15935 my $width;
643 12086 50       23305 $width = $self->data()->{'wx'}->{$glyph} if defined $glyph;
644              
645 12086   100     20717 $width //= $self->missingwidth();
646 12086   100     19667 $width //= 300;
647              
648 12086         26270 return $width;
649             }
650              
651             =item $flag = $font->wxMissingByEnc($char)
652              
653             Return true if the character's width (based on the current encoding) is
654             supplied by "missing width" of font.
655              
656             =cut
657              
658             sub wxMissingByEnc {
659 0     0 1 0 my ($self, $char) = @_;
660              
661 0         0 my $glyph = $self->glyphByEnc($char);
662 0         0 my $width = $self->data()->{'wx'}->{$glyph};
663              
664 0         0 return !defined($width);
665             }
666              
667             =item $width = $font->wxByMap($char)
668              
669             Return the character's width based on the font's default encoding.
670             This is a value, that when divided by 1000 and multiplied by
671             the font size (height in points), gives the advance width to the
672             next character's start. Typically, the width will be under 1000.
673              
674             =cut
675              
676             sub wxByMap {
677 0     0 1 0 my ($self, $char) = @_;
678              
679 0         0 my $glyph;
680 0 0       0 $glyph = $self->glyphByMap($char) if defined $char;
681 0         0 my $width;
682 0 0       0 $width = $self->data()->{'wx'}->{$glyph} if defined $glyph;
683 0   0     0 $width //= $self->missingwidth();
684 0   0     0 $width //= 300;
685              
686 0         0 return $width;
687             }
688              
689             =item $wd = $font->width($text)
690              
691             Return the width of $text as if it were at size 1.
692             B works only correctly if a proper Perl string
693             is used, either in native or UTF-8 format (check utf8-flag).
694              
695             =cut
696              
697             sub width {
698 192     192 1 396 my ($self, $text) = @_;
699              
700 192 100       509 $text = $self->strByUtf($text) if utf8::is_utf8($text);
701              
702 192         309 my @cache;
703 192         310 my $width = 0;
704 192   33     442 my $kern = $self->{'-dokern'} && ref($self->data()->{'kern'});
705 192         291 my $last_glyph = '';
706 192         586 foreach my $n (unpack('C*',$text)) {
707 2391   66     4368 $cache[$n] //= $self->wxByEnc($n);
708 2391         3074 $width += $cache[$n];
709 2391 50       4161 if ($kern) {
710 0         0 my $glyph = $self->data()->{'e2n'}->[$n];
711 0   0     0 $width += ($self->data()->{'kern'}->{$last_glyph . ':' . $glyph} // 0);
712 0         0 $last_glyph = $glyph;
713             }
714             }
715 192         375 $width /= 1000;
716 192         599 return $width;
717             }
718              
719             =item @widths = $font->width_array($text)
720              
721             Return (as an array) the widths of the words in $text as if they were at size 1.
722              
723             =cut
724              
725             sub width_array {
726 0     0 1 0 my ($self, $text) = @_;
727              
728 0 0       0 $text = $self->utfByStr($text) unless utf8::is_utf8($text);
729 0         0 my @widths = map { $self->width($_) } split(/\s+/, $text);
  0         0  
730 0         0 return @widths;
731             }
732              
733             =back
734              
735             =head1 STRING METHODS
736              
737             =over
738              
739             =item $utf8string = $font->utfByStr($string)
740              
741             Return the utf8-string from string based on the font's encoding map.
742              
743             =cut
744              
745             sub utfByStr {
746 0     0 1 0 my ($self, $string) = @_;
747              
748 0         0 $string = pack('U*', map { $self->uniByEnc($_) } unpack('C*', $string));
  0         0  
749 0         0 utf8::upgrade($string);
750 0         0 return $string;
751             }
752              
753             =item $string = $font->strByUtf($utf8_string)
754              
755             Return the encoded string from utf8-string based on the font's encoding map.
756              
757             =cut
758              
759             sub strByUtf {
760 9     9 1 16 my ($self, $utf8_string) = @_;
761              
762 9         26 $utf8_string = pack('C*', map { $self->encByUni($_) & 0xFF } unpack('U*', $utf8_string));
  27         49  
763 9         31 utf8::downgrade($utf8_string);
764 9         18 return $utf8_string;
765             }
766              
767             =item $pdf_string = $font->textByStr($string)
768              
769             Return a properly formatted representation of $string for use in the PDF.
770              
771             =cut
772              
773             sub textByStr {
774 31     31 1 78 my ($self, $string) = @_;
775              
776 31 50       112 if (not defined $string) { $string = ''; }
  0         0  
777 31 100       109 $string = $self->strByUtf($string) if utf8::is_utf8($string);
778 31         63 my $text = $string;
779 31         100 $text =~ s/\\/\\\\/go;
780 31         93 $text =~ s/([\x00-\x1f])/sprintf('\%03lo',ord($1))/ge;
  0         0  
781 31         70 $text =~ s/([\{\}\[\]\(\)])/\\$1/g;
782              
783 31         80 return $text;
784             }
785              
786             =item $pdf_string = $font->textByStrKern($string)
787              
788             Return a properly formatted representation of $string, with kerning,
789             for use in the PDF.
790              
791             =cut
792              
793             sub textByStrKern {
794 0     0 1 0 my ($self, $string) = @_;
795              
796 0 0 0     0 return '(' . $self->textByStr($string) . ')' unless $self->{'-dokern'} && ref($self->data()->{'kern'});
797 0 0       0 $string = $self->strByUtf($string) if utf8::is_utf8($string);
798              
799 0         0 my $text = ' ';
800 0         0 my $tBefore = 0;
801 0         0 my $last_glyph = '';
802              
803 0         0 foreach my $n (unpack('C*', $string)) {
804 0         0 my $glyph = $self->data()->{'e2n'}->[$n];
805 0 0       0 if (defined $self->data()->{'kern'}->{$last_glyph . ':' . $glyph}) {
806 0 0       0 $text .= ') ' if $tBefore;
807 0         0 $text .= sprintf('%i ', -($self->data()->{'kern'}->{$last_glyph . ':' . $glyph}));
808 0         0 $tBefore = 0;
809             }
810 0         0 $last_glyph = $glyph;
811 0         0 my $t = pack('C', $n);
812 0         0 $t =~ s/\\/\\\\/go;
813 0         0 $t =~ s/([\x00-\x1f])/sprintf('\%03lo',ord($1))/ge;
  0         0  
814 0         0 $t =~ s/([\{\}\[\]\(\)])/\\$1/g;
815 0 0       0 $text .= '(' unless $tBefore;
816 0         0 $text .= "$t";
817 0         0 $tBefore = 1;
818             }
819 0 0       0 $text .= ') ' if $tBefore;
820 0         0 return $text;
821             }
822              
823             # Maintainer's note: $size here is used solely as a flag to determine whether or
824             # not to append a text-showing operator (TJ or Tj).
825             sub text {
826 31     31 0 95 my ($self, $string, $size, $indent) = @_;
827 31 50       106 if (not defined $string) { $string = ''; }
  0         0  
828 31         131 my $text = $self->textByStr($string);
829              
830 31 50 33     200 if (defined $size && $self->{'-dokern'}) {
    50          
831 0         0 $text = $self->textByStrKern($string);
832 0 0       0 return "[ $indent $text ] TJ" if $indent;
833 0         0 return "[ $text ] TJ";
834             } elsif (defined $size) {
835 31 100       169 return "[ $indent ($text) ] TJ" if $indent;
836 19         126 return "($text) Tj";
837             } else {
838             # will need a later Tj operator to actually see this!
839 0         0 return "($text)";
840             }
841             }
842              
843             sub isvirtual {
844 32     32 0 115 return;
845             }
846              
847             =back
848              
849             =cut
850              
851             1;