source: main/trunk/greenstone2/perllib/cpan/Image/ExifTool/WritePostScript.pl@ 34921

Last change on this file since 34921 was 34921, checked in by anupama, 3 years ago

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

File size: 26.7 KB
Line 
1#------------------------------------------------------------------------------
2# File: WritePostScript.pl
3#
4# Description: Write PostScript meta information
5#
6# Revisions: 03/03/2006 - P. Harvey Created
7#
8# References: (see references in PostScript.pm, plus:)
9# 1) http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
10# 2) http://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf
11# 3) http://partners.adobe.com/public/developer/en/acrobat/sdk/pdf/pdf_creation_apis_and_specs/pdfmarkReference.pdf
12# 4) http://www.npes.org/standards/Tools/DCS20Spec.pdf
13#
14# Notes: (see NOTES in POD doc below)
15#------------------------------------------------------------------------------
16
17package Image::ExifTool::PostScript;
18
19use strict;
20
21# Structure of a DSC PS/EPS document:
22#
23# %!PS-Adobe-3.0 [plus " EPSF-3.0" for EPS]
24# <comments>
25# %%EndComments [optional]
26# %%BeginXxxx
27# <stuff to ignore>
28# %%EndXxxx
29# %%BeginProlog
30# <prolog stuff>
31# %%EndProlog
32# %%BeginSetup
33# <setup stuff>
34# %%EndSetup
35# %ImageData x x x x [written by Photoshop]
36# %BeginPhotoshop: xxxx
37# <ascii-hex IRB information>
38# %EndPhotosop
39# %%BeginICCProfile: (name) <num> <type>
40# <ICC Profile info>
41# %%EndICCProfile
42# %begin_xml_code
43# <postscript code to define and read the XMP stream object>
44# %begin_xml_packet: xxxx
45# <XMP data>
46# %end_xml_packet
47# <postscript code to add XMP stream to dictionary>
48# %end_xml_code
49# %%Page: x x [PS only (optional?)]
50# <graphics commands>
51# %%PageTrailer
52# %%Trailer
53# <a bit more code to bracket EPS content for distiller>
54# %%EOF
55
56# map of where information is stored in PS image
57my %psMap = (
58 XMP => 'PostScript',
59 Photoshop => 'PostScript',
60 IPTC => 'Photoshop',
61 EXIFInfo => 'Photoshop',
62 IFD0 => 'EXIFInfo',
63 IFD1 => 'IFD0',
64 ICC_Profile => 'PostScript',
65 ExifIFD => 'IFD0',
66 GPS => 'IFD0',
67 SubIFD => 'IFD0',
68 GlobParamIFD => 'IFD0',
69 PrintIM => 'IFD0',
70 InteropIFD => 'ExifIFD',
71 MakerNotes => 'ExifIFD',
72);
73
74
75#------------------------------------------------------------------------------
76# Write XMP directory to file, with begin/end tokens if necessary
77# Inputs: 0) outfile ref, 1) flags hash ref, 2-N) data to write
78# Returns: true on success
79sub WriteXMPDir($$@)
80{
81 my $outfile = shift;
82 my $flags = shift;
83 my $success = 1;
84 Write($outfile, "%begin_xml_code$/") or $success = 0 unless $$flags{WROTE_BEGIN};
85 Write($outfile, @_) or $success = 0;
86 Write($outfile, "%end_xml_code$/") or $success = 0 unless $$flags{WROTE_BEGIN};
87 return $success;
88}
89
90#------------------------------------------------------------------------------
91# Write a directory inside a PS document
92# Inputs: 0) ExifTool object ref, 1) output file reference,
93# 2) Directory name, 3) data reference, 4) flags hash ref
94# Returns: 0=error, 1=nothing written, 2=dir written ok
95sub WritePSDirectory($$$$$)
96{
97 my ($et, $outfile, $dirName, $dataPt, $flags) = @_;
98 my $success = 2;
99 my $len = $dataPt ? length($$dataPt) : 0;
100 my $create = $len ? 0 : 1;
101 my %dirInfo = (
102 DataPt => $dataPt,
103 DataLen => $len,
104 DirStart => 0,
105 DirLen => $len,
106 DirName => $dirName,
107 Parent => 'PostScript',
108 );
109 # Note: $$flags{WROTE_BEGIN} may be 1 for XMP (it is always 0 for
110 # other dirs, but if 1, the begin/end markers were already written)
111#
112# prepare necessary postscript code to support embedded XMP
113#
114 my ($beforeXMP, $afterXMP, $reportedLen);
115 if ($dirName eq 'XMP' and $len) {
116 # isolate the XMP
117 pos($$dataPt) = 0;
118 unless ($$dataPt =~ /(.*)(<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d)/sg) {
119 $et->Warn('No XMP packet start');
120 return WriteXMPDir($outfile, $flags, $$dataPt);
121 }
122 $beforeXMP = $1;
123 my $xmp = $2;
124 my $p1 = pos($$dataPt);
125 unless ($$dataPt =~ m{<\?xpacket end=.(w|r).\?>}sg) {
126 $et->Warn('No XMP packet end');
127 return WriteXMPDir($outfile, $flags, $$dataPt);
128 }
129 my $p2 = pos($$dataPt);
130 $xmp .= substr($$dataPt, $p1, $p2-$p1);
131 $afterXMP = substr($$dataPt, $p2);
132 # determine if we can adjust the XMP size
133 if ($beforeXMP =~ /%begin_xml_packet: (\d+)/s) {
134 $reportedLen = $1;
135 my @matches= ($beforeXMP =~ /\b$reportedLen\b/sg);
136 undef $reportedLen unless @matches == 2;
137 }
138 # must edit in place if we can't reliably change the XMP length
139 $dirInfo{InPlace} = 1 unless $reportedLen;
140 # process XMP only
141 $dirInfo{DataLen} = $dirInfo{DirLen} = length $xmp;
142 $dirInfo{DataPt} = \$xmp;
143 }
144 my $tagTablePtr = Image::ExifTool::GetTagTable("Image::ExifTool::${dirName}::Main");
145 my $val = $et->WriteDirectory(\%dirInfo, $tagTablePtr);
146 if (defined $val) {
147 $dataPt = \$val; # use modified directory
148 $len = length $val;
149 } elsif ($dirName eq 'XMP') {
150 return 1 unless $len;
151 # just write the original XMP
152 return WriteXMPDir($outfile, $flags, $$dataPt);
153 }
154 unless ($len) {
155 return 1 if $create or $dirName ne 'XMP'; # nothing to create
156 # it would be really difficult to delete the XMP,
157 # so instead we write a blank XMP record
158 $val = <<EMPTY_XMP;
159<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'?>
160<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool $Image::ExifTool::VERSION'>
161</x:xmpmeta>
162EMPTY_XMP
163 $val .= ((' ' x 100) . "\n") x 24 unless $$et{OPTIONS}{Compact}{NoPadding};
164 $val .= q{<?xpacket end='w'?>};
165 $dataPt = \$val;
166 $len = length $val;
167 }
168#
169# write XMP directory
170#
171 if ($dirName eq 'XMP') {
172 if ($create) {
173 # create necessary PS/EPS code to support XMP
174 $beforeXMP = <<HDR_END;
175/pdfmark where {pop true} {false} ifelse
176/currentdistillerparams where {pop currentdistillerparams
177/CoreDistVersion get 5000 ge } {false} ifelse
178and not {userdict /pdfmark /cleartomark load put} if
179[/NamespacePush pdfmark
180[/_objdef {exiftool_metadata_stream} /type /stream /OBJ pdfmark
181[{exiftool_metadata_stream} 2 dict begin /Type /Metadata def
182 /Subtype /XML def currentdict end /PUT pdfmark
183/MetadataString $len string def % exact length of metadata
184/TempString 100 string def
185/ConsumeMetadata {
186currentfile TempString readline pop pop
187currentfile MetadataString readstring pop pop
188} bind def
189ConsumeMetadata
190%begin_xml_packet: $len
191HDR_END
192 # note: use q() to get necessary linefeed before %end_xml_packet
193 $afterXMP = q(
194%end_xml_packet
195[{exiftool_metadata_stream} MetadataString /PUT pdfmark
196);
197 if ($$flags{EPS}) {
198 $afterXMP .= <<EPS_AFTER;
199[/Document 1 dict begin
200 /Metadata {exiftool_metadata_stream} def currentdict end /BDC pdfmark
201[/NamespacePop pdfmark
202EPS_AFTER
203 # write this at end of file
204 $$flags{TRAILER} = "[/EMC pdfmark$/";
205 } else { # PS
206 $afterXMP .= <<PS_AFTER;
207[{Catalog} {exiftool_metadata_stream} /Metadata pdfmark
208[/NamespacePop pdfmark
209PS_AFTER
210 }
211 $beforeXMP =~ s{\n}{$/}sg; # use proper newline characters
212 $afterXMP =~ s{\n}{$/}sg;
213 } else {
214 # replace xmp size in PS code
215 $reportedLen and $beforeXMP =~ s/\b$reportedLen\b/$len/sg;
216 }
217 WriteXMPDir($outfile, $flags, $beforeXMP, $$dataPt, $afterXMP) or $success = 0;
218#
219# Write Photoshop or ICC_Profile directory
220#
221 } elsif ($dirName eq 'Photoshop' or $dirName eq 'ICC_Profile') {
222 my ($startToken, $endToken);
223 if ($dirName eq 'Photoshop') {
224 $startToken = "%BeginPhotoshop: $len";
225 $endToken = '%EndPhotoshop';
226 } else {
227 $startToken = '%%BeginICCProfile: (Photoshop Profile) -1 Hex';
228 $endToken = '%%EndICCProfile';
229 }
230 Write($outfile, $startToken, $/) or $success = 0;
231 # write as an ASCII-hex comment
232 my $i;
233 my $wid = 32;
234 for ($i=0; $i<$len; $i+=$wid) {
235 $wid > $len-$i and $wid = $len-$i;
236 my $dat = substr($$dataPt, $i, $wid);
237 Write($outfile, "% ", uc(unpack('H*',$dat)), $/) or $success = 0;
238 }
239 Write($outfile, $endToken, $/) or $success = 0;
240 } else {
241 $et->Warn("Can't write PS directory $dirName");
242 }
243 undef $val;
244 return $success;
245}
246
247#------------------------------------------------------------------------------
248# Encode postscript tag/value
249# Inputs: 0) tag ID, 1) value
250# Returns: postscript comment
251# - adds brackets, escapes special characters, and limits line length
252sub EncodeTag($$)
253{
254 my ($tag, $val) = @_;
255 unless ($val =~ /^\d+$/) {
256 $val =~ s/([()\\])/\\$1/g; # escape brackets and backslashes
257 $val =~ s/\n/\\n/g; # escape newlines
258 $val =~ s/\r/\\r/g; # escape carriage returns
259 $val =~ s/\t/\\t/g; # escape tabs
260 # use octal escape codes for other control characters
261 $val =~ s/([\x00-\x1f\x7f\xff])/sprintf("\\%.3o",ord($1))/ge;
262 $val = "($val)";
263 }
264 my $line = "%%$tag: $val";
265 # postscript line limit is 255 characters (but it seems that
266 # the limit may be 254 characters if the DOS CR/LF is used)
267 # --> split if necessary using continuation comment "%%+"
268 my $n;
269 for ($n=254; length($line)>$n; $n+=254+length($/)) {
270 substr($line, $n, 0) = "$/%%+";
271 }
272 return $line . $/;
273}
274
275#------------------------------------------------------------------------------
276# Write new tags information in comments section
277# Inputs: 0) ExifTool object ref, 1) output file ref, 2) reference to new tag hash
278# Returns: true on success
279sub WriteNewTags($$$)
280{
281 my ($et, $outfile, $newTags) = @_;
282 my $success = 1;
283 my $tag;
284
285 # get XMP hint and remove from tags hash
286 my $xmpHint = $$newTags{XMP_HINT};
287 delete $$newTags{XMP_HINT};
288
289 foreach $tag (sort keys %$newTags) {
290 my $tagInfo = $$newTags{$tag};
291 my $nvHash = $et->GetNewValueHash($tagInfo);
292 next unless $$nvHash{IsCreating};
293 my $val = $et->GetNewValue($nvHash);
294 $et->VerboseValue("+ PostScript:$$tagInfo{Name}", $val);
295 Write($outfile, EncodeTag($tag, $val)) or $success = 0;
296 ++$$et{CHANGED};
297 }
298 # write XMP hint if necessary
299 Write($outfile, "%ADO_ContainsXMP: MainFirst$/") or $success = 0 if $xmpHint;
300
301 %$newTags = (); # all done with new tags
302 return $success;
303}
304
305#------------------------------------------------------------------------------
306# Write PS file
307# Inputs: 0) ExifTool object reference, 1) source dirInfo reference
308# Returns: 1 on success, 0 if this wasn't a valid PS file,
309# or -1 if a write error occurred
310sub WritePS($$)
311{
312 my ($et, $dirInfo) = @_;
313 $et or return 1; # allow dummy access to autoload this package
314 my $tagTablePtr = Image::ExifTool::GetTagTable('Image::ExifTool::PostScript::Main');
315 my $raf = $$dirInfo{RAF};
316 my $outfile = $$dirInfo{OutFile};
317 my $verbose = $et->Options('Verbose');
318 my $out = $et->Options('TextOut');
319 my ($data, $buff, %flags, $err, $mode, $endToken);
320 my ($dos, $psStart, $psNewStart, $xmpHint, @lines);
321
322 $raf->Read($data, 4) == 4 or return 0;
323 return 0 unless $data =~ /^(%!PS|%!Ad|\xc5\xd0\xd3\xc6)/;
324
325 if ($data =~ /^%!Ad/) {
326 # I've seen PS files start with "%!Adobe-PS"...
327 return 0 unless $raf->Read($buff, 6) == 6 and $buff eq "obe-PS";
328 $data .= $buff;
329
330 } elsif ($data =~ /^\xc5\xd0\xd3\xc6/) {
331#
332# process DOS binary PS files
333#
334 # save DOS header then seek ahead and check PS header
335 $raf->Read($dos, 26) == 26 or return 0;
336 $dos = $data . $dos;
337 SetByteOrder('II');
338 $psStart = Get32u(\$dos, 4);
339 unless ($raf->Seek($psStart, 0) and
340 $raf->Read($data, 4) == 4 and $data eq '%!PS')
341 {
342 $et->Error('Invalid PS header');
343 return 1;
344 }
345 $$raf{PSEnd} = $psStart + Get32u(\$dos, 8);
346 my $base = Get32u(\$dos, 20);
347 Set16u(0xffff, \$dos, 28); # ignore checksum
348 if ($base) {
349 my %dirInfo = (
350 Parent => 'PS',
351 RAF => $raf,
352 Base => $base,
353 NoTiffEnd => 1, # no end-of-TIFF check
354 );
355 $buff = $et->WriteTIFF(\%dirInfo);
356 SetByteOrder('II'); # (WriteTIFF may change this)
357 if ($buff) {
358 $buff = substr($buff, $base); # remove header written by WriteTIFF()
359 } else {
360 # error rewriting TIFF, so just copy over original data
361 my $len = Get32u(\$dos, 24);
362 unless ($raf->Seek($base, 0) and $raf->Read($buff, $len) == $len) {
363 $et->Error('Error reading embedded TIFF');
364 return 1;
365 }
366 $et->Warn('Bad embedded TIFF');
367 }
368 Set32u(0, \$dos, 12); # zero metafile pointer
369 Set32u(0, \$dos, 16); # zero metafile length
370 Set32u(length($dos), \$dos, 20); # set TIFF pointer
371 Set32u(length($buff), \$dos, 24); # set TIFF length
372 } elsif (($base = Get32u(\$dos, 12)) != 0) {
373 # copy over metafile section
374 my $len = Get32u(\$dos, 16);
375 unless ($raf->Seek($base, 0) and $raf->Read($buff, $len) == $len) {
376 $et->Error('Error reading metafile section');
377 return 1;
378 }
379 Set32u(length($dos), \$dos, 12); # set metafile pointer
380 } else {
381 $buff = '';
382 }
383 $psNewStart = length($dos) + length($buff);
384 Set32u($psNewStart, \$dos, 4); # set pointer to start of PS
385 Write($outfile, $dos, $buff) or $err = 1;
386 $raf->Seek($psStart + 4, 0); # seek back to where we were
387 }
388#
389# rewrite PostScript data
390#
391 local $/ = GetInputRecordSeparator($raf);
392 unless ($/ and $raf->ReadLine($buff)) {
393 $et->Error('Invalid PostScript data');
394 return 1;
395 }
396 $data .= $buff;
397 unless ($data =~ /^%!PS-Adobe-3\.(\d+)\b/ and $1 < 2) {
398 if ($et->Error("Document does not conform to DSC spec. Metadata may be unreadable by other apps", 2)) {
399 return 1;
400 }
401 }
402 my $psRev = $1; # save PS revision number (3.x)
403 Write($outfile, $data) or $err = 1;
404 $flags{EPS} = 1 if $data =~ /EPSF/;
405
406 # get hash of new information keyed by tagID and directories to add/edit
407 my $newTags = $et->GetNewTagInfoHash($tagTablePtr);
408
409 # figure out which directories we need to write (PostScript takes priority)
410 $et->InitWriteDirs(\%psMap, 'PostScript');
411 my $addDirs = $$et{ADD_DIRS};
412 my $editDirs = $$et{EDIT_DIRS};
413 my %doneDir;
414
415 # set XMP hint flag (1 for adding, 0 for deleting, undef for no change)
416 $xmpHint = 1 if $$addDirs{XMP};
417 $xmpHint = 0 if $$et{DEL_GROUP}{XMP};
418 $$newTags{XMP_HINT} = $xmpHint if $xmpHint; # add special tag to newTags list
419
420 for (;;) {
421 @lines or GetNextLine($raf, \@lines) or last;
422 $data = shift @lines;
423 if ($endToken) {
424 # look for end token
425 if ($data =~ m/^$endToken\s*$/is) {
426 undef $endToken;
427 # found end: process this information
428 if ($mode) {
429 $doneDir{$mode} and $et->Error("Multiple $mode directories", 1);
430 $doneDir{$mode} = 1;
431 WritePSDirectory($et, $outfile, $mode, \$buff, \%flags) or $err = 1;
432 # write end token if we wrote the begin token
433 Write($outfile, $data) or $err = 1 if $flags{WROTE_BEGIN};
434 undef $buff;
435 } else {
436 Write($outfile, $data) or $err = 1;
437 }
438 } else {
439 # buffer data in current begin/end block
440 if (not defined $mode) {
441 # pick up XMP in unrecognized blocks for editing in place
442 if ($data =~ /^<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d/ and
443 $$editDirs{XMP})
444 {
445 $buff = $data;
446 $mode = 'XMP';
447 } else {
448 Write($outfile, $data) or $err = 1;
449 }
450 } elsif ($mode eq 'XMP') {
451 $buff .= $data;
452 } else {
453 # data is ASCII-hex encoded
454 $data =~ tr/0-9A-Fa-f//dc; # remove all but hex characters
455 $buff .= pack('H*', $data); # translate from hex
456 }
457 }
458 next;
459 } elsif ($data =~ m{^(%{1,2})(Begin)(?!Object:)(.*?)[:\x0d\x0a]}i) {
460 # comments section is over... write any new tags now
461 WriteNewTags($et, $outfile, $newTags) or $err = 1 if %$newTags;
462 undef $xmpHint;
463 # the beginning of a data block (can only write XMP and Photoshop)
464 my %modeLookup = (
465 _xml_code => 'XMP',
466 photoshop => 'Photoshop',
467 iccprofile => 'ICC_Profile',
468 );
469 $verbose > 1 and print $out "$2$3\n";
470 $endToken = $1 . ($2 eq 'begin' ? 'end' : 'End') . $3;
471 $mode = $modeLookup{lc($3)};
472 if ($mode and $$editDirs{$mode}) {
473 $buff = ''; # initialize buffer for this block
474 $flags{WROTE_BEGIN} = 0;
475 } else {
476 undef $mode; # not editing this directory
477 Write($outfile, $data) or $err = 1;
478 $flags{WROTE_BEGIN} = 1;
479 }
480 next;
481 } elsif ($data =~ /^%%(?!Page:|PlateFile:|BeginObject:)(\w+): ?(.*)/s) {
482 # rewrite information from PostScript tags in comments
483 my ($tag, $val) = ($1, $2);
484 # handle Adobe Illustrator files specially
485 # - EVENTUALLY IT WOULD BE BETTER TO FIND ANOTHER IDENTIFICATION METHOD
486 # (because Illustrator doesn't care if the Creator is changed)
487 if ($tag eq 'Creator' and $val =~ /^Adobe Illustrator/) {
488 # disable writing XMP to PostScript-format Adobe Illustrator files
489 # because it confuses Illustrator
490 if ($$editDirs{XMP}) {
491 $et->Warn("Can't write XMP to PostScript-format Illustrator files");
492 # pretend like we wrote it already so we won't try to add it later
493 $doneDir{XMP} = 1;
494 }
495 # don't allow "Creator" to be changed in Illustrator files
496 # (we need it to be able to recognize these files)
497 # --> find a better way to do this!
498 if ($$newTags{$tag}) {
499 $et->Warn("Can't change Postscript:Creator of Illustrator files");
500 delete $$newTags{$tag};
501 }
502 }
503 if ($$newTags{$tag}) {
504 my $tagInfo = $$newTags{$tag};
505 delete $$newTags{$tag}; # write it then forget it
506 next unless ref $tagInfo;
507 # decode comment string (reading continuation lines if necessary)
508 $val = DecodeComment($val, $raf, \@lines, \$data);
509 $val = join $et->Options('ListSep'), @$val if ref $val eq 'ARRAY';
510 my $nvHash = $et->GetNewValueHash($tagInfo);
511 if ($et->IsOverwriting($nvHash, $val)) {
512 $et->VerboseValue("- PostScript:$$tagInfo{Name}", $val);
513 $val = $et->GetNewValue($nvHash);
514 ++$$et{CHANGED};
515 next unless defined $val; # next if tag is being deleted
516 $et->VerboseValue("+ PostScript:$$tagInfo{Name}", $val);
517 $data = EncodeTag($tag, $val);
518 }
519 }
520 # (note: Adobe InDesign doesn't put colon after %ADO_ContainsXMP -- doh!)
521 } elsif (defined $xmpHint and $data =~ m{^%ADO_ContainsXMP:? ?(.+?)[\x0d\x0a]*$}s) {
522 # change the XMP hint if necessary
523 if ($xmpHint) {
524 $data = "%ADO_ContainsXMP: MainFirst$/" if $1 eq 'NoMain';
525 } else {
526 $data = "%ADO_ContainsXMP: NoMain$/";
527 }
528 # delete XMP hint flags
529 delete $$newTags{XMP_HINT};
530 undef $xmpHint;
531 } else {
532 # look for end of comments section
533 if (%$newTags and ($data !~ /^%\S/ or
534 $data =~ /^%(%EndComments|%Page:|%PlateFile:|%BeginObject:|.*BeginLayer)/))
535 {
536 # write new tags at end of comments section
537 WriteNewTags($et, $outfile, $newTags) or $err = 1;
538 undef $xmpHint;
539 }
540 # look for start of drawing commands (AI uses "%AI5_BeginLayer",
541 # and Helios uses "%%BeginObject:")
542 if ($data =~ /^%(%Page:|%PlateFile:|%BeginObject:|.*BeginLayer)/ or
543 $data !~ m{^(%.*|\s*)$}s)
544 {
545 # we have reached the first page or drawing command, so create necessary
546 # directories and copy the rest of the file, then all done
547 my $dir;
548 my $plateFile = ($data =~ /^%%PlateFile:/);
549 # create Photoshop first, then XMP if necessary
550 foreach $dir (qw{Photoshop ICC_Profile XMP}) {
551 next unless $$editDirs{$dir} and not $doneDir{$dir};
552 if ($plateFile) {
553 # PlateFile comments may contain offsets so we can't edit these files!
554 $et->Warn("Can only edit PostScript information DCS Plate files");
555 last;
556 }
557 next unless $$addDirs{$dir} or $dir eq 'XMP';
558 $flags{WROTE_BEGIN} = 0;
559 WritePSDirectory($et, $outfile, $dir, undef, \%flags) or $err = 1;
560 $doneDir{$dir} = 1;
561 }
562 # copy rest of file
563 if ($flags{TRAILER}) {
564 # write trailer before %%EOF
565 for (;;) {
566 Write($outfile, $data) or $err = 1;
567 if (@lines) {
568 $data = shift @lines;
569 } else {
570 $raf->ReadLine($data) or undef($data), last;
571 $dos and CheckPSEnd($raf, \$data);
572 if ($data =~ /[\x0d\x0a]%%EOF\b/g) {
573 # split data before "%%EOF"
574 # (necessary if data contains other newline sequences)
575 my $pos = pos($data) - 5;
576 push @lines, substr($data, $pos);
577 $data = substr($data, 0, $pos);
578 }
579 }
580 last if $data =~ /^%%EOF\b/;
581 }
582 Write($outfile, $flags{TRAILER}) or $err = 1;
583 }
584 # simply copy the rest of the file if any data is left
585 if (defined $data) {
586 Write($outfile, $data) or $err = 1;
587 Write($outfile, @lines) or $err = 1 if @lines;
588 while ($raf->Read($data, 65536)) {
589 $dos and CheckPSEnd($raf, \$data);
590 Write($outfile, $data) or $err = 1;
591 }
592 }
593 last; # all done!
594 }
595 }
596 # write new information or copy existing line
597 Write($outfile, $data) or $err = 1;
598 }
599 if ($dos and not $err) {
600 # must go back and set length of PS section in DOS header (very dumb design)
601 if (ref $outfile eq 'SCALAR') {
602 Set32u(length($$outfile) - $psNewStart, $outfile, 8);
603 } else {
604 my $pos = tell $outfile;
605 unless (seek($outfile, 8, 0) and
606 print $outfile Set32u($pos - $psNewStart) and
607 seek($outfile, $pos, 0))
608 {
609 $et->Error("Can't write DOS-style PS files in non-seekable stream");
610 $err = 1;
611 }
612 }
613 }
614 # issue warning if we couldn't write any information
615 unless ($err) {
616 my (@notDone, $dir);
617 delete $$newTags{XMP_HINT};
618 push @notDone, 'PostScript' if %$newTags;
619 foreach $dir (qw{Photoshop ICC_Profile XMP}) {
620 push @notDone, $dir if $$editDirs{$dir} and not $doneDir{$dir} and
621 not $$et{DEL_GROUP}{$dir};
622 }
623 @notDone and $et->Warn("Couldn't write ".join('/',@notDone).' information');
624 }
625 $endToken and $et->Error("File missing $endToken");
626 return $err ? -1 : 1;
627}
628
629
6301; # end
631
632__END__
633
634=head1 NAME
635
636Image::ExifTool::WritePostScript.pl - Write PostScript meta information
637
638=head1 SYNOPSIS
639
640This file is autoloaded by Image::ExifTool::PostScript.
641
642=head1 DESCRIPTION
643
644This file contains routines to write meta information in PostScript
645documents. Six forms of meta information may be written:
646
647 1) PostScript comments (Adobe DSC specification)
648 2) XMP information embedded in a document-level XMP stream
649 3) EXIF information embedded in a Photoshop record
650 4) IPTC information embedded in a PhotoShop record
651 5) ICC_Profile information embedded in an ICCProfile record
652 6) TIFF information embedded in DOS-style binary header
653
654=head1 NOTES
655
656Currently, information is written only in the outer-level document.
657
658Photoshop will discard meta information in a PostScript document if it has
659to rasterize the image, and it will rasterize anything that doesn't contain
660the Photoshop-specific 'ImageData' tag. So don't expect Photoshop to read
661any meta information added to EPS images that it didn't create.
662
663The following two acronyms may be confusing since they are so similar and
664have different meanings with respect to PostScript documents:
665
666 DSC = Document Structuring Conventions
667 DCS = Desktop Color Separation
668
669=head1 REFERENCES
670
671See references in L<PostScript.pm|Image::ExifTool::PostScript>, plus:
672
673=over 4
674
675=item L<http://www.adobe.com/products/postscript/pdfs/PLRM.pdf>
676
677=item L<http://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf>
678
679=item L<http://partners.adobe.com/public/developer/en/acrobat/sdk/pdf/pdf_creation_apis_and_specs/pdfmarkReference.pdf>
680
681=back
682
683=head1 ACKNOWLEDGEMENTS
684
685Thanks to Tim Kordick for his help testing the EPS writer.
686
687=head1 AUTHOR
688
689Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
690
691This library is free software; you can redistribute it and/or modify it
692under the same terms as Perl itself.
693
694=head1 SEE ALSO
695
696L<Image::ExifTool::PostScript(3pm)|Image::ExifTool::PostScript>,
697L<Image::ExifTool(3pm)|Image::ExifTool>
698
699=cut
Note: See TracBrowser for help on using the repository browser.