File Coverage

blib/lib/PostScript/File/Metrics.pm
Criterion Covered Total %
statement 114 152 75.0
branch 42 76 55.2
condition 18 37 48.6
subroutine 14 29 48.2
pod 20 20 100.0
total 208 314 66.2


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package PostScript::File::Metrics;
3             #
4             # Copyright 2009 Christopher J. Madsen
5             #
6             # Author: Christopher J. Madsen
7             # Created: 29 Oct 2009
8             #
9             # This program is free software; you can redistribute it and/or modify
10             # it under the same terms as Perl itself.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
15             # GNU General Public License or the Artistic License for more details.
16             #
17             # ABSTRACT: Metrics for PostScript fonts
18             #---------------------------------------------------------------------
19              
20 14     14   205688 use 5.008;
  14         36  
  14         555  
21             our $VERSION = '2.11'; ## no critic
22             # This file is part of PostScript-File 2.21 (May 2, 2015)
23              
24 14     14   60 use strict;
  14         16  
  14         379  
25 14     14   49 use warnings;
  14         16  
  14         321  
26 14     14   50 use Carp qw(carp croak);
  14         15  
  14         898  
27 14     14   6783 use Encode qw(find_encoding);
  14         98035  
  14         1049  
28              
29 14     14   8093 use PostScript::File ':metrics_methods'; # Import some methods
  14         37  
  14         3640  
30              
31             our (%Info, %Metrics);
32              
33             #=====================================================================
34             # Generate accessor methods:
35              
36             BEGIN {
37 14     14   33 my ($code, $error, $success) = '';
38 14         19 foreach my $attribute (qw(
39             full_name
40             family
41             weight
42             fixed_pitch
43             italic_angle
44             version
45             )) {
46 84         125 $code .= "sub $attribute { shift->{info}{$attribute} };\n";
47             }
48              
49 14         20 foreach my $attribute (qw(
50             underline_position
51             underline_thickness
52             cap_height
53             x_height
54             ascender
55             descender
56             )) {
57 84         137 $code .= <<"END SUB";
58             sub $attribute {
59             my \$self = shift;
60             my \$v = \$self->{info}{$attribute};
61             defined \$v ? \$v * \$self->{factor} : \$v;
62             }
63             END SUB
64             }
65              
66 14         19 { local $@;
  14         19  
67 14 0   0 1 3646 $success = eval "$code ; 'OK'"; ## no critic ProhibitStringyEval
  0 0   0 1 0  
  0 0   0 1 0  
  0 0   0 1 0  
  0 0   0 1 0  
  0 0   0 1 0  
  0     0 1 0  
  0     0 1 0  
  0     0 1 0  
  0     0 1 0  
  0     0 1 0  
  0     0 1 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  
  0         0  
68 14         31 $error = $@;
69             } # end local $@
70              
71 14 50 33     15486 unless ($success and $success eq 'OK') {
72 0   0     0 $error ||= 'eval died with false $@';
73 0         0 die "$code\n$error";
74             }
75             } # end BEGIN
76              
77             #---------------------------------------------------------------------
78             sub font_bbox
79             {
80 0     0 1 0 my $self = shift;
81              
82 0         0 my $bbox = $self->{info}{font_bbox};
83              
84 0 0       0 if (1 != (my $f = $self->{factor})) {
85 0         0 [ map { $_ * $f } @$bbox ];
  0         0  
86             } else {
87 0         0 $bbox;
88             }
89             } # end font_bbox
90              
91             #---------------------------------------------------------------------
92 0     0 1 0 sub auto_hyphen { shift->{auto_hyphen} }
93 0     0 1 0 sub size { shift->{size} }
94              
95             #=====================================================================
96              
97              
98             sub new
99             {
100 13     13 1 817 my ($class, $font, $size, $encoding) = @_;
101              
102 13 0 33     81 $encoding ||= ($font eq 'Symbol' ? 'sym' : 'std');
103              
104             # Load the metrics if necessary:
105 13 50       64 unless ($Metrics{$font}{$encoding}) {
106             # First, try to load a pre-compiled package:
107 13         48 my $package = _get_package_name($font, $encoding);
108              
109             ## no critic (ProhibitStringyEval)
110 13 50 33     21 unless (do { local $@; eval "require $package; 1" }
  13         22  
  13         1109  
111             and $Metrics{$font}{$encoding}) {
112             # No pre-compiled package, we'll have to read the AFM file:
113             ## use critic
114 0         0 require PostScript::File::Metrics::Loader;
115              
116 0         0 PostScript::File::Metrics::Loader::load($font, [$encoding]);
117             } # end unless metrics have been pre-generated
118             } # end unless the metrics are loaded
119              
120             # Create the Metrics object:
121 13   33     104 my $self = bless {
122             info => $Info{$font},
123             metrics => $Metrics{$font}{$encoding}
124             || croak "Failed to load metrics for $font in encoding $encoding",
125             }, $class;
126              
127 13 50 33     98 $self->{encoding} = find_encoding($encoding)
128             or croak "Unknown encoding $encoding"
129             unless $encoding =~ /^(?:std|sym)$/;
130 13         2329 $self->set_auto_hyphen(1);
131 13         43 $self->set_size($size);
132 13         42 $self->set_wrap_chars;
133             } # end new
134             #---------------------------------------------------------------------
135              
136              
137             sub set_size
138             {
139 13     13 1 22 my ($self, $size) = @_;
140              
141 13   50     42 $self->{size} = $size || 1000;
142              
143 13 50       58 $self->{factor} = ($size ? $size/1000.0 : 1);
144              
145 13         17 $self;
146             } # end set_size
147             #---------------------------------------------------------------------
148              
149              
150             sub width
151             {
152 157     157 1 98 my $self = shift; # $string
153              
154 157 50 33     401 return 0.0 unless defined $_[0] and length $_[0];
155              
156 157         128 my $wx = $self->{metrics};
157              
158 157 0       162 my $string = $_[1] ? $_[0] : $self->encode_text(
    50          
159             $self->{auto_hyphen} ? $self->convert_hyphens($_[0]) : $_[0]
160             );
161              
162 157         97 my $width = 0;
163 157         592 $width += $wx->[$_] for unpack("C*", $string);
164              
165 157         332 $width * $self->{factor};
166             } # end width
167             #---------------------------------------------------------------------
168              
169              
170             sub wrap
171             {
172 15     15 1 12262 my $self = shift;
173 15         18 my $width = shift;
174 15         16 my $text = shift;
175 15 100       28 my %param = @_ ? %{+shift} : ();
  4         15  
176              
177 15         16 my $maxlines = delete $param{maxlines};
178 15         11 my $quiet = delete $param{quiet};
179 15         15 my $warnings = delete $param{warnings};
180 15 100       29 my $re = (exists($param{chars})
181             ? $self->_build_wrap_re(delete $param{chars})
182             : $self->{wrap_re});
183              
184 15 50       27 carp "Unknown wrap parameter(s) @{[ keys %param ]}" if %param;
  0         0  
185              
186             # Remove CRs; convert ZWSP to CR:
187 15         24 $text =~ s/\r//g;
188 15 100       39 $text =~ s/\x{200B}/\r/g if Encode::is_utf8($text);
189              
190 15 50       50 $text = $self->encode_text(
191             $self->{auto_hyphen} ? $self->convert_hyphens($text) : $text
192             );
193              
194             # Do word wrapping:
195 15         37 my @lines = '';
196              
197 15         17 for ($text) {
198 136 100       192 if (m/\G[ \t\r]*\n/gc) {
199 6         7 push @lines, '';
200             } else {
201 130 100       490 m/\G($re)/g or last;
202 118         128 my $word = $1;
203             check_word:
204 155 100       230 if ($self->width($lines[-1] . $word, 1) <= $width) {
    100          
205 117         121 $lines[-1] .= $word;
206             } elsif ($lines[-1] eq '') {
207 1         2 $lines[-1] = $word;
208 1         2 my $w = sprintf("%s is too wide (%g) for field width %g",
209             $word, $self->width($word, 1), $width);
210 1 50       3 push @$warnings, $w if $warnings;
211 1 50       101 carp $w unless $quiet;
212             } else {
213 37         34 push @lines, '';
214 37         61 $word =~ s/^[ \t\r]+//;
215 37         155 goto check_word;
216             }
217             } # end else not at LF
218              
219 124 100 100     259 if (defined $maxlines and @lines >= $maxlines) {
220 3 50       12 $lines[-1] .= $1 if m/\G(.*[^ \t\r\n])/sg;
221 3 100 66     15 if (($warnings or not $quiet) and
      66        
222             (my $linewidth = $self->width($lines[-1], 1)) > $width) {
223 1         12 my $w = sprintf("'%s' is too wide (%g) for field width %g",
224             $lines[-1], $linewidth, $width);
225 1 50       4 push @$warnings, $w if $warnings;
226 1 50       150 carp $w unless $quiet;
227             } # end if issuing warning about last line
228 3         74 last;
229             } # end if reached maximum number of lines
230              
231 121         69 redo; # Only the "last" statement above can exit
232             } # end for $text
233              
234             # Remove any remaining CR (ZWSP) chars:
235 15         42 s/\r//g for @lines;
236              
237             # Remove the last line if it's blank ($text ended with newline):
238 15 100 100     50 pop @lines unless @lines == 1 or length $lines[-1];
239              
240 15 50       19 if ($self->{auto_hyphen}) {
241             # At this point, any hyphen-minus characters are unambiguously
242             # MINUS SIGN. Protect them from further processing:
243 15         19 map { $self->decode_text($_, 1) } @lines;
  57         80  
244             } else {
245 0         0 @lines;
246             }
247             } # end wrap
248             #---------------------------------------------------------------------
249              
250              
251             sub set_wrap_chars
252             {
253 13     13 1 17 my $self = shift;
254              
255 13         44 $self->{wrap_re} = $self->_build_wrap_re(@_);
256              
257 13         53 $self;
258             } # end set_wrap_chars
259              
260             #---------------------------------------------------------------------
261             our %_wrap_re_cache;
262              
263             sub _build_wrap_re
264             {
265 14     14   27 my ($self, $chars) = @_;
266              
267 14 100       41 if (not defined $chars) {
268 13         20 $chars = '-/';
269 13 50       38 if ($self->{encoding}) {
270 13         22 $chars .= "\xAD";
271             # Only cp1252 has en dash & em dash:
272 13 100       145 $chars .= "\x{2013}\x{2014}" if $self->{encoding}->name eq 'cp1252';
273             }
274             } # end if $chars not supplied (use default)
275              
276 14         87 $chars = $self->encode_text($chars);
277              
278 14   33     73 return $_wrap_re_cache{$chars} ||= do {
279 14 100       40 if (length $chars) {
280 13         61 $chars =~ s/(.)/ sprintf '\x%02X', ord $1 /seg;
  41         129  
281              
282 13         685 qr(
283             [ \t\r]*
284             (?: [^$chars \t\r\n]+ |
285             [$chars]+ [^$chars \t\r\n]* )
286             [$chars]*
287             )x;
288             } else {
289 1         5 qr( [ \t\r]* [^ \t\r\n]+ )x;
290             }
291             };
292             } # end _build_wrap_re
293              
294             #---------------------------------------------------------------------
295             # Return the package in which the font's metrics are stored:
296              
297             sub _get_package_name
298             {
299 13     13   25 my ($font, $encoding) = @_;
300              
301 13         47 my $package = $encoding;
302 13         65 $package =~ s/-/_/g;
303 13         36 $package .= " $font";
304 13         83 $package =~ s/\W+/::/g;
305              
306 13         42 "PostScript::File::Metrics::$package";
307             } # end _get_package_name
308              
309             #=====================================================================
310             # Package Return Value:
311              
312             1;
313              
314             __END__