File Coverage

blib/lib/PostScript/PrinterFontMetrics.pm
Criterion Covered Total %
statement 128 168 76.1
branch 17 44 38.6
condition 13 30 43.3
subroutine 27 43 62.7
pod 6 15 40.0
total 191 300 63.6


line stmt bran cond sub pod time code
1             # RCS Status : $Id: PrinterFontMetrics.pm,v 1.7 2003-10-23 14:12:04+02 jv Exp $
2             # Author : Andrew Ford
3             # Created On : March 2001
4             # Last Modified By: Johan Vromans
5             # Last Modified On: Thu Oct 23 14:12:02 2003
6             # Update Count : 45
7             # Status : Development
8              
9             ################ Module Preamble ################
10              
11             package PostScript::PrinterFontMetrics;
12              
13 1     1   31295 use strict;
  1         2  
  1         28  
14 1     1   6 use Carp;
  1         2  
  1         83  
15 1     1   1008 use Data::Dumper;
  1         32079  
  1         208  
16              
17 1     1   85 BEGIN { require 5.005; }
18              
19 1     1   1188 use IO qw(File);
  1         1594  
  1         7  
20 1     1   14122 use File::Spec;
  1         2  
  1         31  
21              
22 1     1   6 use vars qw($VERSION @ISA $AUTOLOAD);
  1         2  
  1         106  
23             $VERSION = "0.08";
24              
25 1     1   985 use PostScript::FontMetrics;
  1         5  
  1         58  
26 1     1   929 use PostScript::StandardEncoding;
  1         3  
  1         49  
27              
28             @ISA = qw(PostScript::FontMetrics);
29              
30             # Definitions of the PFM file layout
31              
32 1     1   6 use constant PFM_HEADER_LENGTH => 117;
  1         2  
  1         81  
33 1     1   5 use constant PFM_HEADER_TEMPLATE => 'vVa60vvvvvvvCCCvCvvCvvCCCCvVVVV';
  1         2  
  1         126  
34             my @pfm_header_fields
35             = (
36             'dfVersion',
37             'dfSize',
38             'dfCopyright',
39             'dfType',
40             'dfPoint',
41             'dfVertRes',
42             'dfHorisRes',
43             'dfAscent', # ATM FontBBox (fourth element)
44             'dfInternalLeading',
45             'dfExternalLeading',
46             'dfItalic',
47             'dfUnderline',
48             'dfStrikeOut',
49             'dfWeight',
50             'dfCharSet',
51             'dfPixWidth',
52             'dfPixHeight',
53             'dfPitchAndFamily',
54             'dfAvgWidth',
55             'dfMaxWidth',
56             'dfFirstChar',
57             'dfLastChar',
58             'dfBreakChar',
59             'dfWidthBytes',
60             'dfDevice',
61             'dfFace',
62             'dfBitsPointer',
63             'dfBitsOffset',
64             );
65              
66             # The PFM Extension table just contains offsets of other structures
67             # and so its members are not entered into the data hash
68            
69 1     1   6 use constant PFM_EXTENSION_OFFSET => PFM_HEADER_LENGTH;
  1         2  
  1         63  
70 1     1   6 use constant PFM_EXTENSION_LENGTH => 30;
  1         2  
  1         64  
71 1     1   13 use constant PFM_EXTENSION_TEMPLATE => 'vVVVVVVV';
  1         2  
  1         102  
72             my @pfm_extension_fields
73             = (
74             'dfSizeFields', # size of this section
75             'dfExtMetricsOffset', # offset of the Extended Text Metrics section
76             'dfExtentTable', # offset of the Extent table
77             'dfOriginTable',
78             'dfPairKernTable', # offset of the kern pair table
79             'dfTrackKernTable',
80             'dfDriverInfo',
81             'dfReserved'
82             );
83              
84 1     1   7 use constant PFM_EXT_METRICS_OFFSET => PFM_EXTENSION_OFFSET + PFM_EXTENSION_LENGTH;
  1         2  
  1         57  
85 1     1   6 use constant PFM_EXT_METRICS_LENGTH => 52;
  1         3  
  1         72  
86 1     1   6 use constant PFM_EXT_METRICS_TEMPLATE => 'v' x 26;
  1         2  
  1         145  
87             my @pfm_ext_metrics_fields
88             = (
89             'etmSize',
90             'etmPointSize',
91             'etmOrientation',
92             'etmMasterHeight',
93             'etmMinScale',
94             'etmMaxScale',
95             'etmMasterUnits',
96             'etmCapHeight', # AFM CapHeight
97             'etmXHeight', # AFM XHeight
98             'etmLowerCaseAscent', # AFM Ascender
99             'etmLowerCaseDescent', # AFM -Descender
100             'etmSlant',
101             'etmSuperScript',
102             'etmSubScript',
103             'etmSuperScriptSize',
104             'etmSubScriptSize',
105             'etmUnderlineOffset', # AFM -UnderlinePostition
106             'etmUnderlineWidth', # AFM UnderlineThickness
107             'etmDoubleUpperUnderlineOffset',
108             'etmDoubleLowerUnderlineOffset',
109             'etmDoubleUpperUnderlineWidth',
110             'etmDoubleLowerUnderlineWidth',
111             'etmStrikeOutOffset',
112             'etmStrikeOutWidth',
113             'etmKernPairs', # number of kern pairs
114             'etmKernTracks',
115             );
116              
117 1     1   6 use constant PFM_PSINFO_OFFSET => PFM_EXT_METRICS_OFFSET + PFM_EXT_METRICS_LENGTH;
  1         1  
  1         2615  
118             my @pfm_postscript_info_fields
119             = (
120             'DeviceType',
121             'WindowsName',
122             'PostScriptName', # AFM FontName
123             );
124              
125             my %pfm_field_map = ( map { $_ => \&_pfm_header } @pfm_header_fields,
126             map { $_ => \&_pfm_extension } @pfm_extension_fields,
127             map { $_ => \&_pfm_ext_metrics } @pfm_ext_metrics_fields,
128             map { $_ => \&_pfm_device_section } @pfm_postscript_info_fields );
129              
130             sub new {
131 1     1 1 36 my $class = shift;
132 1         4 my $font = shift;
133 1         7 my %atts = ( error => 'die',
134             verbose => 0, trace => 0, debug => 0,
135             @_ );
136 1         4 my $self = { file => $font };
137 1         3 bless $self, $class;
138              
139 1 50       4 return $self unless defined $font;
140              
141 1         9 $self->{debug} = $atts{debug};
142 1   33     7 $self->{trace} = $self->{debug} || $atts{trace};
143 1   33     8 $self->{verbose} = $self->{trace} || $atts{verbose};
144              
145 1         4 my $error = lc($atts{error});
146             $self->{die} = sub {
147 0 0   0   0 die(@_) if $error eq "die";
148 0 0       0 warn(@_) if $error eq "warn";
149 1         7 };
150              
151 1         2 eval { $self->_loadpfm };
  1         5  
152 1 50       4 if ( $@ ) {
153 0         0 $self->_die($@);
154 0         0 return undef;
155             }
156              
157 1         5 $self;
158             }
159              
160 0     0 1 0 sub FileName { return $_[0]->{file}; }
161 0     0 0 0 sub FontName { return $_[0]->_pfm_device_section->{PostScriptName}; }
162 0     0 0 0 sub CapHeight { return $_[0]->_pfm_extended_text_metrics->{etmCapHeight}; }
163 0     0 0 0 sub XHeight { return $_[0]->_pfm_extended_text_metrics->{etmXHeight}; }
164 0     0 0 0 sub Ascender { return $_[0]->_pfm_extended_text_metrics->{etmLowerCaseAscent}; }
165 0     0 0 0 sub Descender { return -$_[0]->_pfm_extended_text_metrics->{etmLowerCaseDescent}; }
166 0     0 1 0 sub MetricsData { return $_[0]->{_rawdata}; }
167              
168 0     0 0 0 sub HeaderFields { return @pfm_header_fields; }
169 0     0 0 0 sub ExtensionFields { return @pfm_extension_fields; }
170 0     0 0 0 sub ExtMetricsFields { return @pfm_ext_metrics_fields; }
171 0     0 0 0 sub DeviceInfoFields { return @pfm_postscript_info_fields; }
172              
173             sub CharWidthData {
174 1     1 1 2 my $self = shift;
175 1         5 my $char = $self->_pfm_header->{dfFirstChar};
176 1   33     474 my $encoding =
177             $self->{encodingvector} ||= PostScript::StandardEncoding->array;
178 1         2 my $widthdata = {};
179 1         3 foreach my $width (@{$self->_pfm_extent_table}) {
  1         5  
180 224         243 my $char = $encoding->[$char++];
181 224 100 66     906 $widthdata->{$char} = $width if $char and $char ne '.notdef';
182             }
183 1         4 $widthdata;
184             }
185              
186             sub EncodingVector {
187 1     1 1 2 my $self = shift;
188 1   33     7 $self->{encodingvector} ||= PostScript::StandardEncoding->array;
189             }
190              
191             sub KernData {
192 1     1 1 7 my $self = shift;
193 1         3 my $raw_kerndata = $self->_pfm_kerndata;
194 1         2 my %enc_kerndata;
195              
196 1   33     10 my $encoding =
197             $self->{encodingvector} ||= PostScript::StandardEncoding->array;
198 1         6 while (my($pair, $kern) = each(%$raw_kerndata)) {
199 998         1836 my($c1,$c2) = unpack("aa", $pair);
200 998         3576 $enc_kerndata{$encoding->[ord $c1], $encoding->[ord $c2]} = $kern;
201             }
202 1         6 return \%enc_kerndata;
203             }
204              
205             # _loadpfm just reads the PFM file into memory (as the _rawdata element)
206             # The individual file sections are only unpacked as and when they are needed.
207              
208             sub _loadpfm ($) {
209 1     1   2 my($self) = @_;
210              
211 1         2 my $fn = $self->{file};
212 1         3 local *FH; # font file
213 1         16 my $sz = $self->{filesize} = -s $fn; # file size
214              
215 1 50       33 open(FH, $fn) || $self->_die("$fn: $!\n");
216 1 50       4 print STDERR ("$fn: Loading PFM file\n") if $self->{verbose};
217 1         4 binmode(FH); # requires a file handle, yuck
218              
219             # Read in the pfm data.
220 1         2 my $len = 0;
221              
222 1 50       32 unless ( ($len = sysread (FH, $self->{_rawdata}, $sz, 0)) == $sz ) {
223 0         0 $self->_die("$fn: Expecting $sz bytes, got $len bytes\n");
224             }
225             }
226              
227             # Return the PFM file Header section (unpacking if necessary)
228              
229             sub _pfm_header {
230 2     2   2 my $self = shift;
231 2   100     29 my $header = $self->{_pfm_header} ||= {};
232              
233 2 100       7 if (! keys %$header) {
234 1         32 @$header{@pfm_header_fields}
235             = unpack(PFM_HEADER_TEMPLATE,
236             substr($self->{_rawdata}, 0, PFM_HEADER_LENGTH));
237 1         8 $header->{dfCopyright} =~ s/\0.*//;
238              
239 1 50       5 die "$self->{file}: file size is $self->{filesize} but dfSize = $header->{dfsize}"
240             unless $header->{dfSize} == $self->{filesize};
241             }
242 2         5 $header;
243             }
244              
245             # Unpack the PFM Extension
246              
247             sub _pfm_extension {
248 2     2   5 my $self = shift;
249 2   100     10 my $extension = $self->{_pfm_extension} ||= {};
250              
251 2 100       5 if (! keys %$extension) {
252 1         19 @$extension{@pfm_extension_fields}
253             = unpack(PFM_EXTENSION_TEMPLATE,
254             substr($self->{_rawdata}, PFM_EXTENSION_OFFSET, PFM_EXTENSION_LENGTH));
255             }
256 2         6 $extension;
257             }
258              
259             # Unpack PFM extended text metrics
260              
261             sub _pfm_extended_text_metrics {
262 0     0   0 my $self = shift;
263 0         0 my $ext_metrics = $self->{_ext_metrics} = {};
264 0 0       0 if (! keys %$ext_metrics) {
265 0         0 my $extension = $self->_pfm_extension;
266 0   0     0 my $ext_metrics_offset = $extension->{dfExtMetricsOffset}
267             || PFM_EXT_METRICS_OFFSET;
268 0 0       0 die "$self->{file}: dfDriverInfo points outside file"
269             if $ext_metrics_offset > $self->{filesize};
270              
271 0         0 @$ext_metrics{@pfm_ext_metrics_fields}
272             = unpack(PFM_EXT_METRICS_TEMPLATE,
273             substr($self->{_rawdata}, $ext_metrics_offset, PFM_EXT_METRICS_LENGTH));
274 0 0       0 delete($extension->{dfExtMetricsOffset})
275             unless $self->{_keep_raw_data};
276             # delete($self->{_rawdata}) unless keys $self->{_pfm_extension}
277             }
278             $ext_metrics
279 0         0 }
280              
281             # Unpack the PFM Device Section
282              
283             sub _pfm_device_section {
284 0     0   0 my $self = shift;
285 0         0 my $psinfo = $self->{_pfm_device} = {};
286 0 0       0 if (! keys %$psinfo) {
287 0         0 my $header = $self->_pfm_header;
288 0   0     0 my $psinfo_offset = $header->{dfDevice} || PFM_PSINFO_OFFSET;
289 0 0       0 die "$self->{file}: dfDevice points outside file ($psinfo_offset > $self->{filesize})"
290             if $psinfo_offset > $self->{filesize};
291 0         0 @$psinfo{@pfm_postscript_info_fields}
292             = split(/\0/, substr($self->{_rawdata}, $psinfo_offset), 4);
293             }
294 0         0 $psinfo;
295             }
296              
297             # Extent table (2 bytes x (1 + dfLastChar - dfFirstChar))
298             # location is defined by dfExtentTable field in extension table
299              
300             sub _pfm_extent_table {
301 1     1   7 my $self = shift;
302 1   50     7 my $extent_table = $self->{_pfm_extent_table} ||= [];
303 1 50       3 if (! @$extent_table) {
304 1         5 my $header = $self->_pfm_header;
305 1         4 my $extension = $self->_pfm_extension;
306 1         2 my $extent_offset = $extension->{dfExtentTable};
307 1         2 my $extent_entries = 1 + $header->{dfLastChar} - $header->{dfFirstChar};
308 1         61 @$extent_table = unpack('v' x $extent_entries,
309             substr($self->{_rawdata}, $extent_offset));
310             }
311 1         8 $extent_table;
312             }
313              
314             # Return the raw kern data (unpacking if necessary)
315              
316             sub _pfm_kerndata {
317 1     1   1 my $self = shift;
318 1   50     8 my $kerndata = $self->{_pfm_kerndata} ||= {};
319 1 50       4 if (! keys %$kerndata) {
320 1         4 my $extension = $self->_pfm_extension;
321 1         2 my $kerntable_offset = $extension->{dfPairKernTable};
322 1 50       4 if ($kerntable_offset) {
323 1         3 my($kerntable_len) = unpack('v', substr($self->{_rawdata},
324             $kerntable_offset, 2));
325 1         391 foreach (unpack('a4' x $kerntable_len,
326             substr($self->{_rawdata},
327             $kerntable_offset + 2,
328             4 * $kerntable_len))) {
329 998         1722 my($pair, $kern) = unpack('a2v', $_);
330 998 100       1719 $kern -= 65536 if $kern > 32768;
331 998         2209 $kerndata->{$pair} = int $kern;
332             }
333             }
334             }
335 1         61 $kerndata;
336             }
337              
338             sub AUTOLOAD {
339 0 0   0     return if $AUTOLOAD =~ /::DESTROY$/;
340 0 0         die "Undefined subroutine $AUTOLOAD"
341             unless exists $pfm_field_map{$AUTOLOAD};
342 0           my $struct = &{$pfm_field_map{$AUTOLOAD}};
  0            
343 0           $struct->{$AUTOLOAD};
344             }
345              
346             sub _die {
347 0     0     my ($self, @msg) = @_;
348 0           $self->{die}->(@msg);
349             }
350              
351             1;
352              
353             __END__