File Coverage

blib/lib/Image/ExifTool/WritePDF.pl
Criterion Covered Total %
statement 276 391 70.5
branch 169 306 55.2
condition 55 101 54.4
subroutine 7 9 77.7
pod 0 7 0.0
total 507 814 62.2


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: WritePDF.pl
3             #
4             # Description: Write PDF meta information
5             #
6             # Revisions: 12/08/2007 - P. Harvey Created
7             #
8             # References: 1) http://partners.adobe.com/public/developer/pdf/index_reference.html
9             #
10             # Notes: The special "PDF-update" group can be deleted to revert exiftool updates
11             #------------------------------------------------------------------------------
12             package Image::ExifTool::PDF;
13              
14 19     19   130 use strict;
  19         40  
  19         652  
15 19     19   101 use vars qw($lastFetched);
  19         52  
  19         76212  
16              
17             sub WriteObject($$);
18             sub EncodeString($);
19             sub CryptObject($);
20              
21             # comments to mark beginning and end of ExifTool incremental update
22             my $beginComment = '%BeginExifToolUpdate';
23             my $endComment = '%EndExifToolUpdate ';
24              
25             my $keyExt; # crypt key extension
26             my $pdfVer; # version of PDF file we are currently writing
27              
28             # internal tags used in dictionary objects
29             my %myDictTags = (
30             _tags => 1, _stream => 1, _decrypted => 1, _needCrypt => 1,
31             _filtered => 1, _entry_size => 1, _table => 1,
32             );
33              
34             # map for directories that we can add
35             my %pdfMap = (
36             XMP => 'PDF',
37             );
38              
39             #------------------------------------------------------------------------------
40             # Validate raw PDF values for writing (string date integer real boolean name)
41             # Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref
42             # Returns: error string or undef (and possibly changes value) on success
43             sub CheckPDF($$$)
44             {
45 97     97 0 269 my ($et, $tagInfo, $valPtr) = @_;
46 97   66     495 my $format = $$tagInfo{Writable} || $tagInfo->{Table}->{WRITABLE};
47 97 50       464 if (not $format) {
    100          
    50          
    0          
    0          
    0          
    0          
48 0         0 return 'No writable format';
49             } elsif ($format eq 'string') {
50             # (encode later because list-type string tags need to be encoded as a unit)
51             } elsif ($format eq 'date') {
52             # be flexible about this for now
53 31 50       158 return 'Bad date format' unless $$valPtr =~ /^\d{4}/;
54             } elsif ($format eq 'integer') {
55 0 0       0 return 'Not an integer' unless Image::ExifTool::IsInt($$valPtr);
56             } elsif ($format eq 'real') {
57 0 0       0 return 'Not a real number' unless $$valPtr =~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/;
58             } elsif ($format eq 'boolean') {
59 0 0 0     0 $$valPtr = ($$valPtr and $$valPtr !~ /^f/i) ? 'true' : 'false';
60             } elsif ($format eq 'name') {
61 0 0       0 return 'Invalid PDF name' if $$valPtr =~ /\0/;
62             } else {
63 0         0 return "Invalid PDF format '${format}'";
64             }
65 97         339 return undef; # value is OK
66             }
67              
68             #------------------------------------------------------------------------------
69             # Format value for writing to PDF file
70             # Inputs: 0) ExifTool ref, 1) value, 2) format string (string,date,integer,real,boolean,name)
71             # Returns: formatted value or undef on error
72             # Notes: Called at write time, so $pdfVer may be checked
73             sub WritePDFValue($$$)
74             {
75 13     13 0 30 my ($et, $val, $format) = @_;
76 13 50       34 if (not $format) {
    100          
    50          
    0          
    0          
77 0         0 return undef;
78             } elsif ($format eq 'string') {
79             # encode as UCS2 if it contains any special characters
80 11 50       28 $val = "\xfe\xff" . $et->Encode($val,'UCS2','MM') if $val =~ /[\x80-\xff]/;
81 11         24 EncodeString(\$val);
82             } elsif ($format eq 'date') {
83             # convert date to "D:YYYYmmddHHMMSS+-HH'MM'" format
84 2         18 $val =~ s/([-+]\d{2}):(\d{2})/${1}'${2}'/; # change timezone delimiters if necessary
85 2         6 $val =~ tr/ ://d; # remove spaces and colons
86 2         4 $val = "D:$val"; # add leading "D:"
87 2         6 EncodeString(\$val);
88             } elsif ($format =~ /^(integer|real|boolean)$/) {
89             # no reformatting necessary
90             } elsif ($format eq 'name') {
91 0 0       0 return undef if $val =~ /\0/;
92 0 0       0 if ($pdfVer >= 1.2) {
93 0         0 $val =~ s/([\t\n\f\r ()<>[\]{}\/%#])/sprintf('#%.2x',ord $1)/sge;
  0         0  
94             } else {
95 0 0       0 return undef if $val =~ /[\t\n\f\r ()<>[\]{}\/%]/;
96             }
97 0         0 $val = "/$val"; # add leading '/'
98             } else {
99 0         0 return undef;
100             }
101 13         26 return $val;
102             }
103              
104             #------------------------------------------------------------------------------
105             # Encode PDF string
106             # Inputs: 0) reference to PDF string
107             # Returns: (updates string with encoded data)
108             sub EncodeString($)
109             {
110 13     13 0 16 my $strPt = shift;
111 13 50       29 if (ref $$strPt eq 'ARRAY') {
112 0         0 my $str;
113 0         0 foreach $str (@{$$strPt}) {
  0         0  
114 0         0 EncodeString(\$str);
115             }
116 0         0 return;
117             }
118 13         44 Crypt($strPt, $keyExt, 1); # encrypt if necessary
119             # encode as hex if we have any control characters (except tab)
120 13 50       28 if ($$strPt=~/[\x00-\x08\x0a-\x1f\x7f\xff]/) {
121             # encode as hex
122 0         0 my $str='';
123 0         0 my $len = length $$strPt;
124 0         0 my $i = 0;
125 0         0 for (;;) {
126 0 0       0 my $n = $len - $i or last;
127 0 0       0 $n = 40 if $n > 40; # break into reasonable-length lines
128 0 0       0 $str .= $/ if $i;
129 0         0 $str .= unpack('H*', substr($$strPt, $i, $n));
130 0         0 $i += $n;
131             }
132 0         0 $$strPt = "<$str>";
133             } else {
134 13         23 $$strPt =~ s/([()\\])/\\$1/g; # must escape round brackets and backslashes
135 13         31 $$strPt = "($$strPt)";
136             }
137             }
138              
139             #------------------------------------------------------------------------------
140             # Encrypt an object
141             # Inputs: 0) PDF object (encrypts in place)
142             # Notes: Encrypts according to "_needCrypt" dictionary entry,
143             # then deletes "_needCrypt" when done
144             sub CryptObject($)
145             {
146 0     0 0 0 my $obj = $_[0];
147 0 0       0 if (not ref $obj) {
    0          
    0          
148             # only literal strings and hex strings are encrypted
149 0 0       0 if ($obj =~ /^[(<]/) {
150 0         0 undef $lastFetched; # (reset this just in case)
151 0         0 my $val = ReadPDFValue($obj);
152 0         0 EncodeString(\$val);
153 0         0 $_[0] = $val;
154             }
155             } elsif (ref $obj eq 'HASH') {
156 0         0 my $tag;
157 0         0 my $needCrypt = $$obj{_needCrypt};
158 0         0 foreach $tag (keys %$obj) {
159 0 0       0 next if $myDictTags{$tag};
160             # re-encrypt necessary objects only (others are still encrypted)
161             # (this is really annoying, but is necessary because objects stored
162             # in encrypted streams are decrypted when extracting, but strings stored
163             # as direct objects are decrypted later since they must be decoded
164             # before being decrypted)
165 0 0       0 if ($needCrypt) {
166 0 0       0 next unless defined $$needCrypt{$tag} ? $$needCrypt{$tag} : $$needCrypt{'*'};
    0          
167             }
168 0         0 CryptObject($$obj{$tag});
169             }
170 0         0 delete $$obj{_needCrypt}; # avoid re-re-crypting
171             } elsif (ref $obj eq 'ARRAY') {
172 0         0 my $val;
173 0         0 foreach $val (@$obj) {
174 0         0 CryptObject($val);
175             }
176             }
177             }
178              
179             #------------------------------------------------------------------------------
180             # Get free entries from xref stream dictionary that we wrote previously
181             # Inputs: 0) xref dictionary reference
182             # Returns: free entry hash (keys are object numbers, values are xref entry list refs)
183             sub GetFreeEntries($)
184             {
185 0     0 0 0 my $dict = shift;
186 0         0 my %xrefFree;
187             # from the start we have only written xref stream entries in 'CNn' format,
188             # so we can simplify things for now and only support this type of entry
189 0         0 my $w = $$dict{W};
190 0 0 0     0 if (ref $w eq 'ARRAY' and "@$w" eq '1 4 2') {
191 0         0 my $size = $$dict{_entry_size}; # this will be 7 for 'CNn'
192 0         0 my $index = $$dict{Index};
193 0         0 my $len = length $$dict{_stream};
194             # scan the table for free objects
195 0         0 my $num = scalar(@$index) / 2;
196 0         0 my $pos = 0;
197 0         0 my ($i, $j);
198 0         0 for ($i=0; $i<$num; ++$i) {
199 0         0 my $start = $$index[$i*2];
200 0         0 my $count = $$index[$i*2+1];
201 0         0 for ($j=0; $j<$count; ++$j) {
202 0 0       0 last if $pos + $size > $len;
203 0         0 my @t = unpack("x$pos CNn", $$dict{_stream});
204             # add entry if object was free
205 0 0       0 $xrefFree{$start+$j} = [ $t[1], $t[2], 'f' ] if $t[0] == 0;
206 0         0 $pos += $size; # step to next entry
207             }
208             }
209             }
210 0         0 return \%xrefFree;
211             }
212              
213             #------------------------------------------------------------------------------
214             # Write PDF object
215             # Inputs: 0) output file or scalar ref, 1) PDF object
216             # Returns: true on success
217             # Notes: inserts white space before object, but none afterward
218             sub WriteObject($$)
219             {
220 208     208 0 311 my ($outfile, $obj) = @_;
221 208 100       479 if (ref $obj eq 'SCALAR') {
    100          
    100          
222 43 50       94 Write($outfile, ' ', $$obj) or return 0;
223             } elsif (ref $obj eq 'ARRAY') {
224             # write array
225 12 50       45 Write($outfile, @$obj > 10 ? $/ : ' ', '[') or return 0;
    50          
226 12         19 my $item;
227 12         24 foreach $item (@$obj) {
228 22 50       44 WriteObject($outfile, $item) or return 0;
229             }
230 12 50       25 Write($outfile, ' ]') or return 0;
231             } elsif (ref $obj eq 'HASH') {
232             # write dictionary
233 47         56 my $tag;
234 47 50       79 Write($outfile, $/, '<<') or return 0;
235             # prepare object as required if it has a stream
236 47 100       102 if ($$obj{_stream}) {
237             # encrypt stream if necessary (must be done before determining Length)
238 9 50       40 CryptStream($obj, $keyExt) if $$obj{_decrypted};
239             # write "Length" entry in dictionary
240 9         17 $$obj{Length} = length $$obj{_stream};
241 9         13 push @{$$obj{_tags}}, 'Length';
  9         24  
242             # delete Filter-related entries since we don't yet write filtered streams
243 9         14 delete $$obj{Filter};
244 9         14 delete $$obj{DecodeParms};
245 9         14 delete $$obj{DL};
246             }
247             # don't write my internal entries
248 47         175 my %wrote = %myDictTags;
249             # write tags in original order, adding new ones later alphabetically
250 47         81 foreach $tag (@{$$obj{_tags}}, sort keys %$obj) {
  47         189  
251             # ignore already-written or missing entries
252 339 100 100     778 next if $wrote{$tag} or not defined $$obj{$tag};
253 139 50       301 Write($outfile, $/, "/$tag") or return 0;
254 139 50       268 WriteObject($outfile, $$obj{$tag}) or return 0;
255 139         242 $wrote{$tag} = 1;
256             }
257 47 50       124 Write($outfile, $/, '>>') or return 0;
258 47 100       124 if ($$obj{_stream}) {
259             # write object stream
260             # (a single 0x0d may not follow 'stream', so use 0x0d+0x0a here to be sure)
261 9 50       27 Write($outfile, $/, "stream\x0d\x0a") or return 0;
262 9 50       27 Write($outfile, $$obj{_stream}, $/, 'endstream') or return 0;
263             }
264             } else {
265             # write string, number, name or object reference
266 106         151 Write($outfile, ' ', $obj);
267             }
268 208         374 return 1;
269             }
270              
271             #------------------------------------------------------------------------------
272             # Write PDF File
273             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
274             # Returns: 1 on success, 0 if not valid PDF file, -1 on write error
275             # Notes: dictionary structure: Main --+--> Info
276             # +--> Root --> Metadata
277             sub WritePDF($$)
278             {
279 19     19 0 39 my ($et, $dirInfo) = @_;
280 19         39 my $raf = $$dirInfo{RAF};
281 19         30 my $outfile = $$dirInfo{OutFile};
282 19         47 my ($buff, %capture, %newXRef, %newObj, $objRef);
283 19         0 my ($out, $id, $gen, $obj);
284              
285             # make sure this is a PDF file
286 19         54 my $pos = $raf->Tell();
287 19 50       58 $raf->Read($buff, 1024) >= 8 or return 0;
288 19 50       114 $buff =~ /^(\s*)%PDF-(\d+\.\d+)/ or return 0;
289 19         61 $$et{PDFBase} = length $1;
290 19         60 $raf->Seek($pos, 0);
291              
292             # create a new ExifTool object and use it to read PDF and XMP information
293 19         114 my $newTool = new Image::ExifTool;
294 19         59 $newTool->Options(List => 1);
295 19         48 $newTool->Options(Password => $et->Options('Password'));
296 19         54 $newTool->Options(NoPDFList => $et->Options('NoPDFList'));
297 19         42 $$newTool{PDF_CAPTURE} = \%capture;
298 19         50 my $info = $newTool->ImageInfo($raf, 'XMP', 'PDF:*', 'Error', 'Warning');
299             # not a valid PDF file unless we got a version number
300             # (note: can't just check $$info{PDFVersion} due to possibility of XMP-pdf:PDFVersion)
301 19         59 my $vers = $newTool->GetInfo('PDF:PDFVersion');
302             # take highest version number if multiple versions in an incremental save
303 19         77 ($pdfVer) = sort { $b <=> $a } values %$vers;
  0         0  
304 19 50       51 $pdfVer or $et->Error('Missing PDF:PDFVersion'), return 0;
305             # check version number
306 19 50       42 if ($pdfVer > 1.7) {
307 0         0 $et->Warn("The PDF $pdfVer specification is not freely available", 1);
308             # (so writing by ExifTool is based on trial and error)
309             }
310             # fail if we had any serious errors while extracting information
311 19 50 33     71 if ($capture{Error} or $$info{Error}) {
312 0   0     0 $et->Error($capture{Error} || $$info{Error});
313 0         0 return 1;
314             }
315             # make sure we have everything we need to rewrite this file
316 19         39 foreach $obj (qw(Main Root xref)) {
317 57 50       117 next if $capture{$obj};
318             # any warning we received may give a clue about why this object is missing
319 0 0       0 $et->Error($$info{Warning}) if $$info{Warning};
320 0         0 $et->Error("Can't find $obj object");
321 0         0 return 1;
322             }
323 19         75 $et->InitWriteDirs(\%pdfMap, 'XMP');
324              
325             # copy file up to start of previous exiftool update or end of file
326             # (comment, startxref & EOF with 11-digit offsets and 2-byte newlines is 63 bytes)
327 19 50 33     67 $raf->Seek(-64,2) and $raf->Read($buff,64) and $raf->Seek(0,0) or return -1;
      33        
328 19         46 my $rtn = 1;
329 19         28 my $prevUpdate;
330             # (now $endComment is before "startxref", but pre-7.41 we wrote it after the EOF)
331 19 100       181 if ($buff =~ /$endComment(\d+)\s+(startxref\s+\d+\s+%%EOF\s+)?$/s) {
    50          
332 16         42 $prevUpdate = $1;
333             # rewrite the file up to the original EOF
334 16 50       92 Image::ExifTool::CopyBlock($raf, $outfile, $prevUpdate + $$et{PDFBase}) or $rtn = -1;
335             # verify that we are now at the start of an ExifTool update
336 16 50 33     60 unless ($raf->Read($buff, length $beginComment) and $buff eq $beginComment) {
337 0         0 $et->Error('Previous ExifTool update is corrupted');
338 0         0 return $rtn;
339             }
340 16 50       53 $raf->Seek($prevUpdate+$$et{PDFBase}, 0) or $rtn = -1;
341 16 100       57 if ($$et{DEL_GROUP}{'PDF-update'}) {
342 2         9 $et->VPrint(0, " Reverted previous ExifTool updates\n");
343 2         4 ++$$et{CHANGED};
344 2         27 return $rtn;
345             }
346             } elsif ($$et{DEL_GROUP}{'PDF-update'}) {
347 0         0 $et->Error('File contains no previous ExifTool update');
348 0         0 return $rtn;
349             } else {
350             # rewrite the whole file
351 3         11 while ($raf->Read($buff, 65536)) {
352 3 50       15 Write($outfile, $buff) or $rtn = -1;
353             }
354             }
355 17 50       64 $out = $et->Options('TextOut') if $et->Options('Verbose');
356             #
357             # create our new PDF objects to write
358             #
359 17         35 my $xref = $capture{xref};
360 17         28 my $mainDict = $capture{Main};
361 17         30 my $metaRef = $capture{Root}->{Metadata};
362 17         32 my $nextObject;
363              
364             # start by finding reference for info object in case it was deleted
365             # in a previous edit so we can re-use it here if adding PDF Info
366             my $prevInfoRef;
367 17 100       40 if ($prevUpdate) {
368 14 50       38 unless ($capture{Prev}) {
369 0         0 $et->Error("Can't locate trailer dictionary prior to last edit");
370 0         0 return $rtn;
371             }
372 14         24 $prevInfoRef = $capture{Prev}->{Info};
373             # start from previous size so the xref table doesn't continue
374             # to grow if we repeatedly add and delete the Metadata object
375 14         26 $nextObject = $capture{Prev}->{Size};
376             # don't re-use Meta reference if object was added in a previous update
377 14 100 66     120 undef $metaRef if $metaRef and $$metaRef=~/^(\d+)/ and $1 >= $nextObject;
      100        
378             } else {
379 3         6 $prevInfoRef = $$mainDict{Info};
380 3         6 $nextObject = $$mainDict{Size};
381             }
382              
383             # delete entire PDF group if specified
384 17         31 my $infoChanged = 0;
385 17 100 100     50 if ($$et{DEL_GROUP}{PDF} and $capture{Info}) {
386 4         16 delete $capture{Info};
387 4         16 $info = { XMP => $$info{XMP} }; # remove extracted PDF tags
388 4 50       12 print $out " Deleting PDF Info dictionary\n" if $out;
389 4         7 ++$infoChanged;
390             }
391              
392             # create new Info dictionary if necessary
393 17 100       58 $capture{Info} = { _tags => [ ] } unless $capture{Info};
394 17         28 my $infoDict = $capture{Info};
395              
396             # must pre-determine Info reference to be used in encryption
397 17   100     69 my $infoRef = $prevInfoRef || \ "$nextObject 0 R";
398 17         33 $keyExt = $$infoRef;
399              
400             # must encrypt all values in dictionary if they came from an encrypted stream
401 17 50       37 CryptObject($infoDict) if $$infoDict{_needCrypt};
402              
403             # must set line separator before calling WritePDFValue()
404 17         70 local $/ = $capture{newline};
405              
406             # rewrite PDF Info tags
407 17         68 my $newTags = $et->GetNewTagInfoHash(\%Image::ExifTool::PDF::Info);
408 17         27 my $tagID;
409 17         59 foreach $tagID (sort keys %$newTags) {
410 11         21 my $tagInfo = $$newTags{$tagID};
411 11         28 my $nvHash = $et->GetNewValueHash($tagInfo);
412 11         17 my (@vals, $deleted);
413 11         19 my $tag = $$tagInfo{Name};
414 11         18 my $val = $$info{$tag};
415 11         18 my $tagKey = $tag;
416 11 100       24 unless (defined $val) {
417             # must check for tag key with copy number
418 9         122 ($tagKey) = grep /^$tag/, keys %$info;
419 9 100       31 $val = $$info{$tagKey} if $tagKey;
420             }
421 11 100       31 if (defined $val) {
    50          
422 7         9 my @oldVals;
423 7 100       16 if (ref $val eq 'ARRAY') {
424 3         8 @oldVals = @$val;
425 3         6 $val = shift @oldVals;
426             }
427 7         9 for (;;) {
428 11 100       29 if ($et->IsOverwriting($nvHash, $val) > 0) {
429 5         7 $deleted = 1;
430 5         20 $et->VerboseValue("- PDF:$tag", $val);
431 5         6 ++$infoChanged;
432             } else {
433 6         12 push @vals, $val;
434             }
435 11 100       22 last unless @oldVals;
436 4         6 $val = shift @oldVals;
437             }
438             # don't write this out if we deleted all values
439 7 100       19 delete $$infoDict{$tagID} unless @vals;
440             } elsif ($$nvHash{EditOnly}) {
441 0         0 next;
442             }
443             # decide whether we want to write this tag
444             # (native PDF information is always preferred, so don't check IsCreating)
445 11 50 100     41 next unless $deleted or $$tagInfo{List} or not exists $$infoDict{$tagID};
      66        
446              
447             # add new values to existing ones
448 11         29 my @newVals = $et->GetNewValue($nvHash);
449 11 100       27 if (@newVals) {
450 9         26 push @vals, @newVals;
451 9         17 ++$infoChanged;
452 9 50       19 if ($out) {
453 0         0 foreach $val (@newVals) {
454 0         0 $et->VerboseValue("+ PDF:$tag", $val);
455             }
456             }
457             }
458 11 50       22 unless (@vals) {
459             # remove this entry from the Info dictionary if no values remain
460 0         0 delete $$infoDict{$tagID};
461 0         0 next;
462             }
463             # format value(s) for writing to PDF file
464 11   66     37 my $writable = $$tagInfo{Writable} || $Image::ExifTool::PDF::Info{WRITABLE};
465 11 100       26 if (not $$tagInfo{List}) {
    100          
466 5         13 $val = WritePDFValue($et, shift(@vals), $writable);
467             } elsif ($$tagInfo{List} eq 'array') {
468 3         8 foreach $val (@vals) {
469 5         11 $val = WritePDFValue($et, $val, $writable);
470 5 50       13 defined $val or undef(@vals), last;
471             }
472 3 50       10 $val = @vals ? \@vals : undef;
473             } else {
474 3         10 $val = WritePDFValue($et, join($et->Options('ListSep'), @vals), $writable);
475             }
476 11 50       31 if (defined $val) {
477 11         24 $$infoDict{$tagID} = $val;
478 11         28 ++$infoChanged;
479             } else {
480 0         0 $et->Warn("Error converting $$tagInfo{Name} value");
481             }
482             }
483 17 100       63 if ($infoChanged) {
    100          
484 10         23 $$et{CHANGED} += $infoChanged;
485             } elsif ($prevUpdate) {
486             # must still write Info dictionary if it was previously updated
487 6         22 my $oldPos = LocateObject($xref, $$infoRef);
488 6 100 66     41 $infoChanged = 1 if $oldPos and $oldPos > $prevUpdate;
489             }
490              
491             # create new Info dictionary if necessary
492 17 100       38 if ($infoChanged) {
493             # increment object count if we used a new object here
494 14 100       18 if (scalar(keys %{$capture{Info}}) > 1) {
  14         52  
495 10         29 $newObj{$$infoRef} = $capture{Info};# save to write later
496 10         21 $$mainDict{Info} = $infoRef; # add reference to trailer dictionary
497 10 100       24 ++$nextObject unless $prevInfoRef;
498             } else {
499             # remove Info from Main (trailer) dictionary
500 4         9 delete $$mainDict{Info};
501             # write free entry in xref table if Info existed prior to all edits
502 4 100       14 $newObj{$$infoRef} = '' if $prevInfoRef;
503             }
504             }
505              
506             # rewrite XMP
507             my %xmpInfo = (
508             DataPt => $$info{XMP},
509 17         62 Parent => 'PDF',
510             );
511 17         50 my $xmpTable = Image::ExifTool::GetTagTable('Image::ExifTool::XMP::Main');
512 17         37 my $oldChanged = $$et{CHANGED};
513 17         64 my $newXMP = $et->WriteDirectory(\%xmpInfo, $xmpTable);
514 17 100       51 $newXMP = $$info{XMP} ? ${$$info{XMP}} : '' unless defined $newXMP;
  4 100       8  
515              
516             # WriteDirectory() will increment CHANGED erroneously if non-existent
517             # XMP is deleted as a block -- so check for this
518 17 100 100     51 unless ($newXMP or $$info{XMP}) {
519 3         5 $$et{CHANGED} = $oldChanged;
520 3         10 $et->VPrint(0, " (XMP not changed -- still empty)\n");
521             }
522 17         32 my ($metaChanged, $rootChanged);
523              
524 17 100 66     87 if ($$et{CHANGED} != $oldChanged and defined $newXMP) {
    100 33        
525 10         18 $metaChanged = 1;
526             } elsif ($prevUpdate and $capture{Root}->{Metadata}) {
527             # must still write Metadata dictionary if it was previously updated
528 4         8 my $oldPos = LocateObject($xref, ${$capture{Root}->{Metadata}});
  4         15  
529 4 50 33     21 $metaChanged = 1 if $oldPos and $oldPos > $prevUpdate;
530             }
531 17 100       37 if ($metaChanged) {
532 14 100       37 if ($newXMP) {
    50          
533 9 100       19 unless ($metaRef) {
534             # allocate new PDF object
535 5         15 $metaRef = \ "$nextObject 0 R";
536 5         9 ++$nextObject;
537 5         12 $capture{Root}->{Metadata} = $metaRef;
538 5         35 $rootChanged = 1; # set flag to replace Root dictionary
539             }
540             # create the new metadata dictionary to write later
541 9         56 $newObj{$$metaRef} = {
542             Type => '/Metadata',
543             Subtype => '/XML',
544             # Length => length $newXMP, (set by WriteObject)
545             _tags => [ qw(Type Subtype) ],
546             _stream => $newXMP,
547             _decrypted => 1, # (this will be ignored if EncryptMetadata is false)
548             };
549             } elsif ($capture{Root}->{Metadata}) {
550             # free existing metadata object
551 5         8 $newObj{${$capture{Root}->{Metadata}}} = '';
  5         14  
552 5         11 delete $capture{Root}->{Metadata};
553 5         8 $rootChanged = 1; # set flag to replace Root dictionary
554             }
555             }
556             # add new Root dictionary if necessary
557 17         36 my $rootRef = $$mainDict{Root};
558 17 50       38 unless ($rootRef) {
559 0         0 $et->Error("Can't find Root dictionary");
560 0         0 return $rtn;
561             }
562 17 100 100     53 if (not $rootChanged and $prevUpdate) {
563             # must still write Root dictionary if it was previously updated
564 6         18 my $oldPos = LocateObject($xref, $$rootRef);
565 6 100 66     28 $rootChanged = 1 if $oldPos and $oldPos > $prevUpdate;
566             }
567 17 100       58 $newObj{$$rootRef} = $capture{Root} if $rootChanged;
568             #
569             # write incremental update if anything was changed
570             #
571 17 100       66 if ($$et{CHANGED}) {
    50          
572             # remember position of original EOF
573 16         50 my $oldEOF = Tell($outfile) - $$et{PDFBase};
574 16 50       36 Write($outfile, $beginComment) or $rtn = -1;
575              
576             # write new objects
577 16         69 foreach $objRef (sort keys %newObj) {
578 38 50       181 $objRef =~ /^(\d+) (\d+)/ or $rtn = -1, last;
579 38         116 ($id, $gen) = ($1, $2);
580 38 100       85 if (not $newObj{$objRef}) {
581 7 50       27 ++$gen if $gen < 65535;
582             # write free entry in xref table
583 7         17 $newXRef{$id} = [ 0, $gen, 'f' ];
584 7         13 next;
585             }
586             # create new entry for xref table
587 31         71 $newXRef{$id} = [ Tell($outfile) - $$et{PDFBase} + length($/), $gen, 'n' ];
588 31         74 $keyExt = "$id $gen obj"; # (must set for stream encryption)
589 31 50       75 Write($outfile, $/, $keyExt) or $rtn = -1;
590 31 50       81 WriteObject($outfile, $newObj{$objRef}) or $rtn = -1;
591 31 50       64 Write($outfile, $/, 'endobj') or $rtn = -1;
592             }
593              
594             # Prev points to old xref table
595 16 100       51 $$mainDict{Prev} = $capture{startxref} unless $prevUpdate;
596              
597             # add xref entry for head of free-object list
598 16         42 $newXRef{0} = [ 0, 65535, 'f' ];
599              
600             # must insert free xref entries from previous exiftool update if applicable
601 16 100       33 if ($prevUpdate) {
602 13         16 my $mainFree;
603             # extract free entries from our previous Main xref stream
604 13 50 33     45 if ($$mainDict{Type} and $$mainDict{Type} eq '/XRef') {
605 0         0 $mainFree = GetFreeEntries($xref->{dicts}->[0]);
606             } else {
607             # free entries from Main xref table already captured for us
608 13         26 $mainFree = $capture{mainFree};
609             }
610 13         46 foreach $id (sort { $a <=> $b } keys %$mainFree) {
  6         21  
611 19 100       54 $newXRef{$id} = $$mainFree{$id} unless $newXRef{$id};
612             }
613             }
614              
615             # connect linked list of free object in our xref table
616 16         28 my $prevFree = 0;
617 16         55 foreach $id (sort { $b <=> $a } keys %newXRef) { # (reverse sort)
  70         108  
618 59 100       118 next unless $newXRef{$id}->[2] eq 'f'; # skip if not free
619             # no need to add free entry for objects added by us
620             # in previous edits then freed again
621 28 100       61 if ($id >= $nextObject) {
622 3         7 delete $newXRef{$id}; # Note: deleting newXRef entry!
623 3         6 next;
624             }
625 25         37 $newXRef{$id}->[0] = $prevFree;
626 25         41 $prevFree = $id;
627             }
628              
629             # prepare our main dictionary for writing
630 16         33 $$mainDict{Size} = $nextObject; # update number of objects
631             # must change the ID if it exists
632 16 100 66     51 if (ref $$mainDict{ID} eq 'ARRAY' and @{$$mainDict{ID}} > 1) {
  6         21  
633             # increment first byte since this is an easy change to make
634 6         12 $id = $mainDict->{ID}->[1];
635 6 50 0     27 if ($id =~ /^<([0-9a-f]{2})/i) {
    0 0        
      0        
636 6         32 my $byte = unpack('H2',chr((hex($1) + 1) & 0xff));
637 6         17 substr($id, 1, 2) = $byte;
638             } elsif ($id =~ /^\((.)/s and $1 ne '\\' and $1 ne ')' and $1 ne '(') {
639 0         0 my $ch = chr((ord($1) + 1) & 0xff);
640             # avoid generating characters that could cause problems
641 0 0       0 $ch = 'a' if $ch =~ /[()\\\x00-\x08\x0a-\x1f\x7f\xff]/;
642 0         0 substr($id, 1, 1) = $ch;
643             }
644 6         13 $mainDict->{ID}->[1] = $id;
645             }
646              
647             # remember position of xref table in file (we will write this next)
648 16         42 my $startxref = Tell($outfile) - $$et{PDFBase} + length($/);
649              
650             # must write xref as a stream in xref-stream-only files
651 16 50 33     50 if ($$mainDict{Type} and $$mainDict{Type} eq '/XRef') {
652              
653             # create entry for the xref stream object itself
654 0         0 $newXRef{$nextObject++} = [ Tell($outfile) - $$et{PDFBase} + length($/), 0, 'n' ];
655 0         0 $$mainDict{Size} = $nextObject;
656             # create xref stream and Index entry
657 0         0 $$mainDict{W} = [ 1, 4, 2 ]; # int8u, int32u, int16u ('CNn')
658 0         0 $$mainDict{Index} = [ ];
659 0         0 $$mainDict{_stream} = '';
660 0         0 my @ids = sort { $a <=> $b } keys %newXRef;
  0         0  
661 0         0 while (@ids) {
662 0         0 my $startID = $ids[0];
663 0         0 for (;;) {
664 0         0 $id = shift @ids;
665 0         0 my ($pos, $gen, $type) = @{$newXRef{$id}};
  0         0  
666 0 0       0 if ($pos > 0xffffffff) {
667 0         0 $et->Error('Huge files not yet supported');
668 0         0 last;
669             }
670 0 0       0 $$mainDict{_stream} .= pack('CNn', $type eq 'f' ? 0 : 1, $pos, $gen);
671 0 0 0     0 last if not @ids or $ids[0] != $id + 1;
672             }
673             # add Index entries for this section of the xref stream
674 0         0 push @{$$mainDict{Index}}, $startID, $id - $startID + 1;
  0         0  
675             }
676             # write the xref stream object
677 0         0 $keyExt = "$id 0 obj"; # (set anyway, but xref stream should NOT be encrypted)
678 0 0       0 Write($outfile, $/, $keyExt) or $rtn = -1;
679 0 0       0 WriteObject($outfile, $mainDict) or $rtn = -1;
680 0 0       0 Write($outfile, $/, 'endobj') or $rtn = -1;
681              
682             } else {
683              
684             # write new xref table
685 16 50       41 Write($outfile, $/, 'xref', $/) or $rtn = -1;
686             # lines must be exactly 20 bytes, so pad newline if necessary
687 16 50       65 my $endl = (length($/) == 1 ? ' ' : '') . $/;
688 16         45 my @ids = sort { $a <=> $b } keys %newXRef;
  63         110  
689 16         42 while (@ids) {
690 34         43 my $startID = $ids[0];
691 34         44 $buff = '';
692 34         37 for (;;) {
693 56         73 $id = shift @ids;
694 56         69 $buff .= sprintf("%.10d %.5d %s%s", @{$newXRef{$id}}, $endl);
  56         175  
695 56 100 100     183 last if not @ids or $ids[0] != $id + 1;
696             }
697             # write this (contiguous-numbered object) section of the xref table
698 34 50       92 Write($outfile, $startID, ' ', $id - $startID + 1, $/, $buff) or $rtn = -1;
699             }
700              
701             # write main (trailer) dictionary
702 16 50       34 Write($outfile, 'trailer') or $rtn = -1;
703 16 50       34 WriteObject($outfile, $mainDict) or $rtn = -1;
704             }
705             # write trailing comment (marker to allow edits to be reverted)
706 16 50       51 Write($outfile, $/, $endComment, $oldEOF, $/) or $rtn = -1;
707              
708             # write pointer to main xref table and EOF marker
709 16 50       48 Write($outfile, 'startxref', $/, $startxref, $/, '%%EOF', $/) or $rtn = -1;
710              
711             } elsif ($prevUpdate) {
712              
713             # nothing new changed, so copy over previous incremental update
714 1 50       5 $raf->Seek($prevUpdate+$$et{PDFBase}, 0) or $rtn = -1;
715 1         5 while ($raf->Read($buff, 65536)) {
716 1 50       5 Write($outfile, $buff) or $rtn = -1;
717             }
718             }
719 17 100 66     125 if ($rtn > 0 and $$et{CHANGED} and ($$et{DEL_GROUP}{PDF} or $$et{DEL_GROUP}{XMP})) {
      100        
      100        
720 6         27 $et->Warn('ExifTool PDF edits are reversible. Deleted tags may be recovered!', 1);
721             }
722 17         173 undef $newTool;
723 17         90 undef %capture;
724 17         296 return $rtn;
725             }
726              
727              
728             1; # end
729              
730             __END__