File Coverage

blib/lib/Image/ExifTool/WPG.pm
Criterion Covered Total %
statement 57 79 72.1
branch 26 52 50.0
condition 10 24 41.6
subroutine 6 6 100.0
pod 0 3 0.0
total 99 164 60.3


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: WPG.pm
3             #
4             # Description: Read WordPerfect Graphics meta information
5             #
6             # Revisions: 2023-05-01 - P. Harvey Created
7             #
8             # References: 1) https://www.fileformat.info/format/wpg/egff.htm
9             # 2) https://archive.org/details/mac_Graphics_File_Formats_Second_Edition_1996/page/n991/mode/2up
10             # 3) http://libwpg.sourceforge.net/
11             #------------------------------------------------------------------------------
12              
13             package Image::ExifTool::WPG;
14              
15 1     1   7352 use strict;
  1         2  
  1         57  
16 1     1   8 use vars qw($VERSION);
  1         3  
  1         58  
17 1     1   8 use Image::ExifTool qw(:DataAccess :Utils);
  1         4  
  1         1383  
18              
19             $VERSION = '1.00';
20              
21             sub PrintRecord($$$);
22              
23             # WPG metadata
24             %Image::ExifTool::WPG::Main = (
25             GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
26             VARS => { ID_FMT => 'none' },
27             NOTES => 'Tags extracted from WordPerfect Graphics (WPG) images.',
28             WPGVersion => { },
29             ImageWidthInches => { PrintConv => 'sprintf("%.2f",$val)' },
30             ImageHeightInches => { PrintConv => 'sprintf("%.2f",$val)' },
31             Records => {
32             Notes => 'records for version 1.0 files',
33             List => 1,
34             PrintHex => 2,
35             PrintConvColumns => 2,
36             PrintConv => {
37             OTHER => \&PrintRecord,
38             0x01 => 'Fill Attributes',
39             0x02 => 'Line Attributes',
40             0x03 => 'Marker Attributes',
41             0x04 => 'Polymarker',
42             0x05 => 'Line',
43             0x06 => 'Polyline',
44             0x07 => 'Rectangle',
45             0x08 => 'Polygon',
46             0x09 => 'Ellipse',
47             0x0a => 'Reserved',
48             0x0b => 'Bitmap (Type 1)',
49             0x0c => 'Graphics Text (Type 1)',
50             0x0d => 'Graphics Text Attributes',
51             0x0e => 'Color Map',
52             0x0f => 'Start WPG (Type 1)',
53             0x10 => 'End WPG',
54             0x11 => 'PostScript Data (Type 1)',
55             0x12 => 'Output Attributes',
56             0x13 => 'Curved Polyline',
57             0x14 => 'Bitmap (Type 2)',
58             0x15 => 'Start Figure',
59             0x16 => 'Start Chart',
60             0x17 => 'PlanPerfect Data',
61             0x18 => 'Graphics Text (Type 2)',
62             0x19 => 'Start WPG (Type 2)',
63             0x1a => 'Graphics Text (Type 3)',
64             0x1b => 'PostScript Data (Type 2)',
65             },
66             },
67             RecordsV2 => {
68             Notes => 'records for version 2.0 files',
69             List => 1,
70             PrintHex => 2,
71             PrintConvColumns => 2,
72             PrintConv => {
73             OTHER => \&PrintRecord,
74             0x00 => 'End Marker',
75             0x01 => 'Start WPG',
76             0x02 => 'End WPG',
77             0x03 => 'Form Settings',
78             0x04 => 'Ruler Settings',
79             0x05 => 'Grid Settings',
80             0x06 => 'Layer',
81             0x08 => 'Pen Style Definition',
82             0x09 => 'Pattern Definition',
83             0x0a => 'Comment',
84             0x0b => 'Color Transfer',
85             0x0c => 'Color Palette',
86             0x0d => 'DP Color Palette',
87             0x0e => 'Bitmap Data',
88             0x0f => 'Text Data',
89             0x10 => 'Chart Style',
90             0x11 => 'Chart Data',
91             0x12 => 'Object Image',
92             0x15 => 'Polyline',
93             0x16 => 'Polyspline',
94             0x17 => 'Polycurve',
95             0x18 => 'Rectangle',
96             0x19 => 'Arc',
97             0x1a => 'Compound Polygon',
98             0x1b => 'Bitmap',
99             0x1c => 'Text Line',
100             0x1d => 'Text Block',
101             0x1e => 'Text Path',
102             0x1f => 'Chart',
103             0x20 => 'Group',
104             0x21 => 'Object Capsule',
105             0x22 => 'Font Settings',
106             0x25 => 'Pen Fore Color',
107             0x26 => 'DP Pen Fore Color',
108             0x27 => 'Pen Back Color',
109             0x28 => 'DP Pen Back Color',
110             0x29 => 'Pen Style',
111             0x2a => 'Pen Pattern',
112             0x2b => 'Pen Size',
113             0x2c => 'DP Pen Size',
114             0x2d => 'Line Cap',
115             0x2e => 'Line Join',
116             0x2f => 'Brush Gradient',
117             0x30 => 'DP Brush Gradient',
118             0x31 => 'Brush Fore Color',
119             0x32 => 'DP Brush Fore Color',
120             0x33 => 'Brush Back Color',
121             0x34 => 'DP Brush Back Color',
122             0x35 => 'Brush Pattern',
123             0x36 => 'Horizontal Line',
124             0x37 => 'Vertical Line',
125             0x38 => 'Poster Settings',
126             0x39 => 'Image State',
127             0x3a => 'Envelope Definition',
128             0x3b => 'Envelope',
129             0x3c => 'Texture Definition',
130             0x3d => 'Brush Texture',
131             0x3e => 'Texture Alignment',
132             0x3f => 'Pen Texture ',
133             }
134             },
135             );
136              
137             #------------------------------------------------------------------------------
138             # Print record type
139             # Inputs: 0) record type and count, 1) inverse flag, 2) PrintConv hash ref
140             # Returns: converted record name
141             sub PrintRecord($$$)
142             {
143 1     1 0 2 my ($val, $inv, $printConv) = @_;
144 1         3 my ($type, $count) = split 'x', $val;
145 1   33     4 my $prt = $$printConv{$type} || sprintf('Unknown (0x%.2x)', $type);
146 1 50       3 $prt .= " x $count" if $count;
147 1         4 return $prt;
148             }
149              
150             #------------------------------------------------------------------------------
151             # Read variable-length integer
152             # Inputs: 0) RAF ref
153             # Returns: integer value
154             sub ReadVarInt($)
155             {
156 12     12 0 12 my $raf = shift;
157 12         23 my $buff;
158 12 50       18 $raf->Read($buff, 1) or return 0;
159 12         13 my $val = ord($buff);
160 12 50       15 if ($val == 0xff) {
161 0 0       0 $raf->Read($buff, 2) == 2 or return 0;
162 0         0 $val = unpack('v', $buff);
163 0 0       0 if ($val & 0x8000) {
164 0 0       0 $raf->Read($buff, 2) == 2 or return 0;
165 0         0 $val = (($val & 0x7fff) << 16) | unpack('v', $buff);
166             }
167             }
168 12         13 return $val;
169             }
170              
171             #------------------------------------------------------------------------------
172             # Read WPG version 1 or 2 image
173             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
174             # Returns: 1 on success, 0 if this wasn't a valid WPG file
175             sub ProcessWPG($$)
176             {
177 1     1 0 3 my ($et, $dirInfo) = @_;
178 1         2 my $raf = $$dirInfo{RAF};
179 1         2 my ($buff, $lastType, $count);
180              
181             # verify this is a valid WPG file
182 1 50       3 return 0 unless $raf->Read($buff, 16) == 16;
183 1 50       3 return 0 unless $buff =~ /^\xff\x57\x50\x43/;
184 1         6 $et->SetFileType();
185 1         10 SetByteOrder('II');
186 1         3 my $tagTablePtr = GetTagTable('Image::ExifTool::WPG::Main');
187 1         5 my $offset = Get32u(\$buff, 4);
188 1         6 my ($ver, $rev) = unpack('x10CC', $buff);
189 1         9 $et->HandleTag($tagTablePtr, WPGVersion => "$ver.$rev");
190 1 50 33     4 if ($ver < 1 or $ver > 2) {
191             # currently support only version 1 and 2 formats
192 0         0 $et->Warn('Unsupported WPG version');
193 0         0 return 1;
194             }
195 1 50       3 my $tag = $ver == 1 ? 'Records' : 'RecordsV2';
196 1 50 0     2 $raf->Seek($offset - 16, 1) or return 1 if $offset > 16;
197             # loop through records
198 1         1 for (;;) {
199 13         31 my ($type, $len, $getSize);
200 13 100       23 if ($raf->Read($buff, $ver) == $ver) { # read 1 or 2 bytes, based on version
201 12 50       14 if ($ver == 1) {
202             # read version 1 record header
203 12         11 $type = ord($buff);
204 12         18 $len = ReadVarInt($raf);
205 12 100       16 $getSize = 1 if $type == 0x0f; # Start WPG (Type 1)
206             } else {
207             # read version 2 record header
208 0         0 $type = unpack('xC', $buff);
209 0         0 ReadVarInt($raf); # skip extensions
210 0         0 $len = ReadVarInt($raf);
211 0 0       0 $getSize = 1 if $type == 0x01; # Start WPG
212 0 0       0 undef $type if $type > 0x3f;
213             }
214 12 100       13 if ($getSize) {
215             # read Start record to obtain image size
216 1 50       4 $raf->Read($buff, $len) == $len or $et->Warn('File format error'), last;
217 1         2 my ($w, $h, $xres, $yres);
218 1 50       2 if ($ver == 1) {
219 1         3 ($w, $h) = unpack('x2vv', $buff);
220             } else {
221 0         0 my ($precision, $format);
222 0         0 ($xres, $yres, $precision) = unpack('vvC', $buff);
223 0 0 0     0 if ($precision == 0 and $len >= 21) {
    0 0        
224 0         0 $format = 'int16s';
225             } elsif ($precision == 1 and $len >= 29) {
226 0         0 $format = 'int32s';
227             } else {
228 0         0 $et->Warn('Invalid integer precision');
229 0         0 next;
230             }
231 0         0 my ($x1,$y1,$x2,$y2) = ReadValue(\$buff, 13, $format, 4, $len-13);
232 0         0 $w = abs($x2 - $x1);
233 0         0 $h = abs($y2 - $y1);
234             }
235 1   50     7 $et->HandleTag($tagTablePtr, ImageWidthInches => $w / ($xres || 1200));
236 1   50     5 $et->HandleTag($tagTablePtr, ImageHeightInches => $h / ($yres || 1200));
237             } else {
238 11 50       19 $raf->Seek($len, 1) or last; # skip to the next record
239             }
240             }
241             # go to some trouble to collapse identical sequential entries in record list
242             # (trying to keep the length of the list managable for complex images)
243 13 100 100     42 $lastType and $type and $type == $lastType and ++$count, next;
      100        
244 9 100       13 if ($lastType) {
245 8 100       21 my $val = $count > 1 ? "${lastType}x$count" : $lastType;
246 8         16 $et->HandleTag($tagTablePtr, $tag => $val);
247             }
248 9 100       17 last unless $type;
249 8         6 $lastType = $type;
250 8         10 $count = 1;
251             }
252 1         5 return 1;
253             }
254              
255             1; # end
256              
257             __END__