File Coverage

blib/lib/PDF/Builder/Resource/UniFont.pm
Criterion Covered Total %
statement 80 101 79.2
branch 23 44 52.2
condition 7 15 46.6
subroutine 9 9 100.0
pod 5 5 100.0
total 124 174 71.2


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::UniFont;
2              
3 2     2   2783 use strict;
  2         4  
  2         64  
4 2     2   7 use warnings;
  2         3  
  2         181  
5              
6             our $VERSION = '3.028'; # VERSION
7             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
8              
9 2     2   10 use Carp;
  2         4  
  2         134  
10 2     2   8 use Encode qw(:all);
  2         3  
  2         2586  
11              
12             =head1 NAME
13              
14             PDF::Builder::Resource::UniFont - Unicode Font Support
15              
16             =head1 METHODS
17              
18             =head2 new
19              
20             $font = PDF::Builder::Resource::UniFont->new($pdf, @fontspecs, %opts)
21              
22             =over
23              
24             Returns a uni-font object.
25              
26             B<FONTSPECS:> fonts can be registered using the following hash-ref:
27              
28             {
29             font => $fontobj, # the font to be registered
30             blocks => $blockspec, # the unicode blocks the font is being registered for
31             codes => $codespec, # the unicode codepoints, -"-
32             }
33              
34             B<BLOCKSPECS:>
35              
36             [
37             $block1, $block3, # register font for block 1 + 3
38             [$blockA, $blockZ], # register font for blocks A .. Z
39             ]
40              
41             B<CODESPECS:>
42              
43             [
44             $cp1, $cp3, # register font for codepoint 1 + 3
45             [$cpA, $cpZ], # register font for codepoints A .. Z
46             ]
47              
48             B<NOTE:> if you want to register a font for the entire unicode space
49             (ie. U+0000 .. U+FFFF), then simply specify a font-object without the hash-ref.
50              
51             =back
52              
53             =head3 Valid options (%opts)
54              
55             =over
56              
57             =item encode
58              
59             Changes the encoding of the font from its default.
60             See "perldoc Encode" for a list of valid tags.
61              
62             =back
63              
64             =cut
65              
66             sub new {
67 1     1 1 2 my $class = shift();
68 1 50       4 $class = ref($class) if ref($class);
69              
70 1         5 my $self = {
71             'fonts' => [],
72             'block' => {},
73             'code' => {},
74             'pdf' => shift(),
75             };
76 1         2 bless $self, $class;
77              
78 1         1 my @fonts;
79 1         7 push @fonts, shift() while ref($_[0]);
80              
81 1         3 my %options = @_;
82             # copy dashed option names to preferred undashed names
83 1 50 33     4 if (defined $options{'-encode'} && !defined $options{'encode'}) { $options{'encode'} = delete($options{'-encode'}); }
  0         0  
84              
85 1 50       10 $self->{'encode'} = $options{'encode'} if defined $options{'encode'};
86             # note that self->encode is undefined if encode not given!
87              
88 1         2 my $font_number = 0;
89 1         2 foreach my $font (@fonts) {
90 2 100       7 if (ref($font) eq 'ARRAY') {
    50          
91 1         2 push @{$self->{'fonts'}}, shift(@$font);
  1         3  
92            
93 1         3 while (defined $font->[0]) {
94 1         1 my $blockspec = shift @$font;
95 1 50       3 if (ref($blockspec)) {
96 1         2 foreach my $block ($blockspec->[0] .. $blockspec->[-1]) {
97 1         26 $self->{'block'}->{$block} = $font_number;
98             }
99             } else {
100 0         0 $self->{'block'}->{$blockspec} = $font_number;
101             }
102             }
103             } elsif (ref($font) eq 'HASH') {
104 0         0 push @{$self->{'fonts'}}, $font->{'font'};
  0         0  
105              
106 0 0 0     0 if (defined $font->{'blocks'} and
107             ref($font->{'blocks'}) eq 'ARRAY') {
108 0         0 foreach my $blockspec (@{$font->{'blocks'}}) {
  0         0  
109 0 0       0 if (ref($blockspec)) {
110 0         0 foreach my $block ($blockspec->[0] .. $blockspec->[-1]) {
111 0         0 $self->{'block'}->{$block} = $font_number;
112             }
113             } else {
114 0         0 $self->{'block'}->{$blockspec} = $font_number;
115             }
116             }
117             }
118              
119 0 0 0     0 if (defined $font->{'codes'} and
120             ref($font->{'codes'}) eq 'ARRAY') {
121 0         0 foreach my $codespec (@{$font->{'codes'}}) {
  0         0  
122 0 0       0 if (ref($codespec)) {
123 0         0 foreach my $code ($codespec->[0] .. $codespec->[-1]) {
124 0         0 $self->{'code'}->{$code} = $font_number;
125             }
126             } else {
127 0         0 $self->{'code'}->{$codespec} = $font_number;
128             }
129             }
130             }
131             } else {
132 1         2 push @{$self->{'fonts'}}, $font;
  1         2  
133 1         3 foreach my $block (0 .. 255) {
134 256         399 $self->{'block'}->{$block} = $font_number;
135             }
136             }
137 2         5 $font_number++;
138             }
139              
140 1         4 return $self;
141             }
142              
143             =head2 isvirtual
144              
145             $flag = $font->isvirtual()
146              
147             =over
148              
149             (No Information)
150              
151             =back
152              
153             =cut
154              
155             sub isvirtual {
156 2     2 1 5 return 1;
157             }
158              
159             =head2 fontlist
160              
161             $font->fontlist()
162              
163             =over
164              
165             (No Information)
166              
167             =back
168              
169             =cut
170              
171             sub fontlist {
172 8     8 1 11 my $self = shift;
173              
174 8         7 return [@{ $self->{'fonts'} }];
  8         41  
175             }
176              
177             =head2 width
178              
179             $w = $font->width($string)
180              
181             =over
182              
183             (No Information)
184              
185             =back
186              
187             =cut
188              
189             sub width {
190 2     2 1 4 my ($self, $text) = @_;
191              
192 2 50       5 if (defined $self->{'encode'}) { # is self->encode guaranteed set?
193 2 100       11 $text = decode($self->{'encode'}, $text) unless utf8::is_utf8($text);
194             }
195 2         186 my $width = 0;
196 2         3 my @blocks = ();
197              
198 2         8 foreach my $u (unpack('U*', $text)) {
199 9         8 my $font_number = 0;
200 9 50       23 if (defined $self->{'code'}->{$u}) {
    50          
201 0         0 $font_number = $self->{'code'}->{$u};
202             } elsif (defined $self->{'block'}->{($u >> 8)}) {
203 9         12 $font_number = $self->{'block'}->{($u >> 8)};
204             } else {
205 0         0 $font_number = 0;
206             }
207 9 100 100     17 if (scalar @blocks == 0 or $blocks[-1]->[0] != $font_number) {
208 3         12 push @blocks, [$font_number, pack('U', $u)];
209             } else {
210 6         11 $blocks[-1]->[1] .= pack('U', $u);
211             }
212             }
213 2         4 foreach my $block (@blocks) {
214 3         5 my ($font_number, $string) = @$block;
215 3         6 $width += $self->fontlist()->[$font_number]->width($string);
216             }
217              
218 2         9 return $width;
219             }
220              
221             =head2 text
222              
223             $font->text($string, $size, $indent)
224              
225             =over
226              
227             (No Information)
228              
229             =back
230              
231             =cut
232              
233             sub text {
234 2     2 1 4 my ($self, $text, $size, $indent) = @_;
235              
236 2 50       5 if (defined $self->{'encode'}) { # is self->encode guaranteed to be defined?
237 2 100       9 $text = decode($self->{'encode'}, $text) unless utf8::is_utf8($text);
238             }
239 2 50       33 croak 'Font size not specified' unless defined $size;
240              
241 2         3 my $newtext = '';
242 2         3 my $last_font_number;
243             my @codes;
244              
245 2         3 foreach my $u (unpack('U*', $text)) {
246 9         9 my $font_number = 0;
247 9 50       19 if (defined $self->{'code'}->{$u}) {
    50          
248 0         0 $font_number = $self->{'code'}->{$u};
249             } elsif (defined $self->{'block'}->{($u >> 8)}) {
250 9         14 $font_number = $self->{'block'}->{($u >> 8)};
251             }
252              
253 9 100 100     19 if (defined $last_font_number and
254             $font_number != $last_font_number) {
255 1         2 my $font = $self->fontlist()->[$last_font_number];
256 1         4 $newtext .= '/' . $font->name() . ' ' . $size. ' Tf ';
257 1         5 $newtext .= $font->text(pack('U*', @codes), $size, $indent) . ' ';
258 1         2 $indent = undef;
259 1         2 @codes = ();
260             }
261              
262 9         22 push @codes, $u;
263 9         11 $last_font_number = $font_number;
264             }
265              
266 2 50       5 if (scalar @codes > 0) {
267 2         3 my $font = $self->fontlist()->[$last_font_number];
268 2         7 $newtext .= '/' . $font->name() . ' ' . $size . ' Tf ';
269 2         13 $newtext .= $font->text(pack('U*', @codes), $size, $indent);
270             }
271              
272 2         6 return $newtext;
273             }
274              
275             1;