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