File Coverage

blib/lib/PostScript/File/Metrics.pm
Criterion Covered Total %
statement 113 151 74.8
branch 42 76 55.2
condition 18 37 48.6
subroutine 14 29 48.2
pod 20 20 100.0
total 207 313 66.1


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   299806 use 5.008;
  14         56  
21             our $VERSION = '2.11'; ## no critic
22             # This file is part of PostScript-File 2.23 (October 10, 2015)
23              
24 14     14   77 use strict;
  14         24  
  14         308  
25 14     14   67 use warnings;
  14         25  
  14         433  
26 14     14   71 use Carp qw(carp croak);
  14         25  
  14         1062  
27 14     14   11959 use Encode qw(find_encoding);
  14         158046  
  14         4419  
28              
29 14     14   17688 use PostScript::File ':metrics_methods'; # Import some methods
  14         45  
  14         4823  
30              
31             our (%Info, %Metrics);
32              
33             #=====================================================================
34             # Generate accessor methods:
35              
36             BEGIN {
37 14     14   36 my ($code, $error, $success) = '';
38 14         38 foreach my $attribute (qw(
39             full_name
40             family
41             weight
42             fixed_pitch
43             italic_angle
44             version
45             )) {
46 84         208 $code .= "sub $attribute { shift->{info}{$attribute} };\n";
47             }
48              
49 14         33 foreach my $attribute (qw(
50             underline_position
51             underline_thickness
52             cap_height
53             x_height
54             ascender
55             descender
56             )) {
57 84         210 $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         29 { local $@;
  14         22  
67 14 0   0 1 5642 $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         42 $error = $@;
69             } # end local $@
70              
71 14 50 33     20649 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 948 my ($class, $font, $size, $encoding) = @_;
101              
102 13 0 33     74 $encoding ||= ($font eq 'Symbol' ? 'sym' : 'std');
103              
104             # Load the metrics if necessary:
105 13 50       78 unless ($Metrics{$font}{$encoding}) {
106             # First, try to load a pre-compiled package:
107 13         59 my $package = _get_package_name($font, $encoding);
108              
109             ## no critic (ProhibitStringyEval)
110 13 50 33     26 unless (do { local $@; eval "require $package; 1" }
  13         31  
  13         1236  
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             my $self = bless {
122             info => $Info{$font},
123 13   33     136 metrics => $Metrics{$font}{$encoding}
124             || croak "Failed to load metrics for $font in encoding $encoding",
125             }, $class;
126              
127 13 50 33     120 $self->{encoding} = find_encoding($encoding)
128             or croak "Unknown encoding $encoding"
129             unless $encoding =~ /^(?:std|sym)$/;
130 13         3584 $self->set_auto_hyphen(1);
131 13         58 $self->set_size($size);
132 13         112 $self->set_wrap_chars;
133             } # end new
134             #---------------------------------------------------------------------
135              
136              
137             sub set_size
138             {
139 13     13 1 33 my ($self, $size) = @_;
140              
141 13   50     63 $self->{size} = $size || 1000;
142              
143 13 50       70 $self->{factor} = ($size ? $size/1000.0 : 1);
144              
145 13         28 $self;
146             } # end set_size
147             #---------------------------------------------------------------------
148              
149              
150             sub width
151             {
152 157     157 1 230 my $self = shift; # $string
153              
154 157 50 33     670 return 0.0 unless defined $_[0] and length $_[0];
155              
156 157         232 my $wx = $self->{metrics};
157              
158             my $string = $_[1] ? $_[0] : $self->encode_text(
159 157 0       287 $self->{auto_hyphen} ? $self->convert_hyphens($_[0]) : $_[0]
    50          
160             );
161              
162 157         197 my $width = 0;
163 157         891 $width += $wx->[$_] for unpack("C*", $string);
164              
165 157         560 $width * $self->{factor};
166             } # end width
167             #---------------------------------------------------------------------
168              
169              
170             sub wrap
171             {
172 15     15 1 17193 my $self = shift;
173 15         23 my $width = shift;
174 15         21 my $text = shift;
175 15 100       42 my %param = @_ ? %{+shift} : ();
  4         16  
176              
177 15         26 my $maxlines = delete $param{maxlines};
178 15         18 my $quiet = delete $param{quiet};
179 15         19 my $warnings = delete $param{warnings};
180             my $re = (exists($param{chars})
181             ? $self->_build_wrap_re(delete $param{chars})
182 15 100       38 : $self->{wrap_re});
183              
184 15 50       38 carp "Unknown wrap parameter(s) @{[ keys %param ]}" if %param;
  0         0  
185              
186             # Remove CRs; convert ZWSP to CR:
187 15         28 $text =~ s/\r//g;
188 15 100       55 $text =~ s/\x{200B}/\r/g if Encode::is_utf8($text);
189              
190             $text = $self->encode_text(
191 15 50       64 $self->{auto_hyphen} ? $self->convert_hyphens($text) : $text
192             );
193              
194             # Do word wrapping:
195 15         54 my @lines = '';
196              
197 15         29 for ($text) {
198 136 100       295 if (m/\G[ \t\r]*\n/gc) {
199 6         13 push @lines, '';
200             } else {
201 130 100       853 m/\G($re)/g or last;
202 118         210 my $word = $1;
203             check_word:
204 155 100       414 if ($self->width($lines[-1] . $word, 1) <= $width) {
    100          
205 117         237 $lines[-1] .= $word;
206             } elsif ($lines[-1] eq '') {
207 1         2 $lines[-1] = $word;
208 1         3 my $w = sprintf("%s is too wide (%g) for field width %g",
209             $word, $self->width($word, 1), $width);
210 1 50       4 push @$warnings, $w if $warnings;
211 1 50       99 carp $w unless $quiet;
212             } else {
213 37         56 push @lines, '';
214 37         96 $word =~ s/^[ \t\r]+//;
215 37         203 goto check_word;
216             }
217             } # end else not at LF
218              
219 124 100 100     432 if (defined $maxlines and @lines >= $maxlines) {
220 3 50       18 $lines[-1] .= $1 if m/\G(.*[^ \t\r\n])/sg;
221 3 100 66     19 if (($warnings or not $quiet) and
      66        
222             (my $linewidth = $self->width($lines[-1], 1)) > $width) {
223 1         13 my $w = sprintf("'%s' is too wide (%g) for field width %g",
224             $lines[-1], $linewidth, $width);
225 1 50       6 push @$warnings, $w if $warnings;
226 1 50       169 carp $w unless $quiet;
227             } # end if issuing warning about last line
228 3         87 last;
229             } # end if reached maximum number of lines
230              
231 121         142 redo; # Only the "last" statement above can exit
232             } # end for $text
233              
234             # Remove any remaining CR (ZWSP) chars:
235 15         62 s/\r//g for @lines;
236              
237             # Remove the last line if it's blank ($text ended with newline):
238 15 100 100     63 pop @lines unless @lines == 1 or length $lines[-1];
239              
240 15 50       39 if ($self->{auto_hyphen}) {
241             # At this point, any hyphen-minus characters are unambiguously
242             # MINUS SIGN. Protect them from further processing:
243 15         24 map { $self->decode_text($_, 1) } @lines;
  57         155  
244             } else {
245 0         0 @lines;
246             }
247             } # end wrap
248             #---------------------------------------------------------------------
249              
250              
251             sub set_wrap_chars
252             {
253 13     13 1 26 my $self = shift;
254              
255 13         62 $self->{wrap_re} = $self->_build_wrap_re(@_);
256              
257 13         70 $self;
258             } # end set_wrap_chars
259              
260             #---------------------------------------------------------------------
261             our %_wrap_re_cache;
262              
263             sub _build_wrap_re
264             {
265 14     14   30 my ($self, $chars) = @_;
266              
267 14 100       56 if (not defined $chars) {
268 13         26 $chars = '-/';
269 13 50       56 if ($self->{encoding}) {
270 13         73 $chars .= "\xAD";
271             # Only cp1252 has en dash & em dash:
272 13 100       195 $chars .= "\x{2013}\x{2014}" if $self->{encoding}->name eq 'cp1252';
273             }
274             } # end if $chars not supplied (use default)
275              
276 14         117 $chars = $self->encode_text($chars);
277              
278 14   33     165 return $_wrap_re_cache{$chars} ||= do {
279 14 100       49 if (length $chars) {
280 13         73 $chars =~ s/(.)/ sprintf '\x%02X', ord $1 /seg;
  41         200  
281              
282 13         946 qr(
283             [ \t\r]*
284             (?: [^$chars \t\r\n]+ |
285             [$chars]+ [^$chars \t\r\n]* )
286             [$chars]*
287             )x;
288             } else {
289 1         6 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   32 my ($font, $encoding) = @_;
300              
301 13         31 my $package = $encoding;
302 13         80 $package =~ s/-/_/g;
303 13         42 $package .= " $font";
304 13         83 $package =~ s/\W+/::/g;
305              
306 13         52 "PostScript::File::Metrics::$package";
307             } # end _get_package_name
308              
309             #=====================================================================
310             # Package Return Value:
311              
312             1;
313              
314             __END__