my
%psMap
= (
XMP
=>
'PostScript'
,
Photoshop
=>
'PostScript'
,
IPTC
=>
'Photoshop'
,
EXIFInfo
=>
'Photoshop'
,
IFD0
=>
'EXIFInfo'
,
IFD1
=>
'IFD0'
,
ICC_Profile
=>
'PostScript'
,
ExifIFD
=>
'IFD0'
,
GPS
=>
'IFD0'
,
SubIFD
=>
'IFD0'
,
GlobParamIFD
=>
'IFD0'
,
PrintIM
=>
'IFD0'
,
InteropIFD
=>
'ExifIFD'
,
MakerNotes
=>
'ExifIFD'
,
);
sub
WriteXMPDir($$@)
{
my
$outfile
=
shift
;
my
$flags
=
shift
;
my
$success
= 1;
Write(
$outfile
,
"%begin_xml_code$/"
) or
$success
= 0
unless
$$flags
{WROTE_BEGIN};
Write(
$outfile
,
@_
) or
$success
= 0;
Write(
$outfile
,
"%end_xml_code$/"
) or
$success
= 0
unless
$$flags
{WROTE_BEGIN};
return
$success
;
}
sub
WritePSDirectory($$$$$)
{
my
(
$et
,
$outfile
,
$dirName
,
$dataPt
,
$flags
) =
@_
;
my
$success
= 2;
my
$len
=
$dataPt
?
length
(
$$dataPt
) : 0;
my
$create
=
$len
? 0 : 1;
my
%dirInfo
= (
DataPt
=>
$dataPt
,
DataLen
=>
$len
,
DirStart
=> 0,
DirLen
=>
$len
,
DirName
=>
$dirName
,
Parent
=>
'PostScript'
,
);
my
(
$beforeXMP
,
$afterXMP
,
$reportedLen
);
if
(
$dirName
eq
'XMP'
and
$len
) {
pos
(
$$dataPt
) = 0;
unless
(
$$dataPt
=~ /(.*)(<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d)/sg) {
$et
->Warn(
'No XMP packet start'
);
return
WriteXMPDir(
$outfile
,
$flags
,
$$dataPt
);
}
$beforeXMP
= $1;
my
$xmp
= $2;
my
$p1
=
pos
(
$$dataPt
);
unless
(
$$dataPt
=~ m{<\?xpacket end=.(w|r).\?>}sg) {
$et
->Warn(
'No XMP packet end'
);
return
WriteXMPDir(
$outfile
,
$flags
,
$$dataPt
);
}
my
$p2
=
pos
(
$$dataPt
);
$xmp
.=
substr
(
$$dataPt
,
$p1
,
$p2
-
$p1
);
$afterXMP
=
substr
(
$$dataPt
,
$p2
);
if
(
$beforeXMP
=~ /
%begin_xml_packet
: (\d+)/s) {
$reportedLen
= $1;
my
@matches
= (
$beforeXMP
=~ /\b
$reportedLen
\b/sg);
undef
$reportedLen
unless
@matches
== 2;
}
$dirInfo
{InPlace} = 1
unless
$reportedLen
;
$dirInfo
{DataLen} =
$dirInfo
{DirLen} =
length
$xmp
;
$dirInfo
{DataPt} = \
$xmp
;
}
my
$tagTablePtr
= Image::ExifTool::GetTagTable(
"Image::ExifTool::${dirName}::Main"
);
my
$val
=
$et
->WriteDirectory(\
%dirInfo
,
$tagTablePtr
);
if
(
defined
$val
) {
$dataPt
= \
$val
;
$len
=
length
$val
;
}
elsif
(
$dirName
eq
'XMP'
) {
return
1
unless
$len
;
return
WriteXMPDir(
$outfile
,
$flags
,
$$dataPt
);
}
unless
(
$len
) {
return
1
if
$create
or
$dirName
ne
'XMP'
;
$val
=
<<EMPTY_XMP;
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool $Image::ExifTool::VERSION'>
</x:xmpmeta>
EMPTY_XMP
$val
.= ((
' '
x 100) .
"\n"
) x 24
unless
$et
->Options(
'Compact'
);
$val
.=
q{<?xpacket end='w'?>}
;
$dataPt
= \
$val
;
$len
=
length
$val
;
}
if
(
$dirName
eq
'XMP'
) {
if
(
$create
) {
$beforeXMP
=
<<HDR_END;
/pdfmark where {pop true} {false} ifelse
/currentdistillerparams where {pop currentdistillerparams
/CoreDistVersion get 5000 ge } {false} ifelse
and not {userdict /pdfmark /cleartomark load put} if
[/NamespacePush pdfmark
[/_objdef {exiftool_metadata_stream} /type /stream /OBJ pdfmark
[{exiftool_metadata_stream} 2 dict begin /Type /Metadata def
/Subtype /XML def currentdict end /PUT pdfmark
/MetadataString $len string def % exact length of metadata
/TempString 100 string def
/ConsumeMetadata {
currentfile TempString readline pop pop
currentfile MetadataString readstring pop pop
} bind def
ConsumeMetadata
%begin_xml_packet: $len
HDR_END
$afterXMP
=
q(
%end_xml_packet
[{exiftool_metadata_stream} MetadataString /PUT pdfmark
)
;
if
(
$$flags
{EPS}) {
$afterXMP
.=
<<EPS_AFTER;
[/Document 1 dict begin
/Metadata {exiftool_metadata_stream} def currentdict end /BDC pdfmark
[/NamespacePop pdfmark
EPS_AFTER
$$flags
{TRAILER} =
"[/EMC pdfmark$/"
;
}
else
{
$afterXMP
.=
<<PS_AFTER;
[{Catalog} {exiftool_metadata_stream} /Metadata pdfmark
[/NamespacePop pdfmark
PS_AFTER
}
$beforeXMP
=~ s{\n}{$/}sg;
$afterXMP
=~ s{\n}{$/}sg;
}
else
{
$reportedLen
and
$beforeXMP
=~ s/\b
$reportedLen
\b/
$len
/sg;
}
WriteXMPDir(
$outfile
,
$flags
,
$beforeXMP
,
$$dataPt
,
$afterXMP
) or
$success
= 0;
}
elsif
(
$dirName
eq
'Photoshop'
or
$dirName
eq
'ICC_Profile'
) {
my
(
$startToken
,
$endToken
);
if
(
$dirName
eq
'Photoshop'
) {
$startToken
=
"%BeginPhotoshop: $len"
;
$endToken
=
'%EndPhotoshop'
;
}
else
{
$startToken
=
'%%BeginICCProfile: (Photoshop Profile) -1 Hex'
;
$endToken
=
'%%EndICCProfile'
;
}
Write(
$outfile
,
$startToken
, $/) or
$success
= 0;
my
$i
;
my
$wid
= 32;
for
(
$i
=0;
$i
<
$len
;
$i
+=
$wid
) {
$wid
>
$len
-
$i
and
$wid
=
$len
-
$i
;
my
$dat
=
substr
(
$$dataPt
,
$i
,
$wid
);
Write(
$outfile
,
"% "
,
uc
(
unpack
(
'H*'
,
$dat
)), $/) or
$success
= 0;
}
Write(
$outfile
,
$endToken
, $/) or
$success
= 0;
}
else
{
$et
->Warn(
"Can't write PS directory $dirName"
);
}
undef
$val
;
return
$success
;
}
sub
EncodeTag($$)
{
my
(
$tag
,
$val
) =
@_
;
unless
(
$val
=~ /^\d+$/) {
$val
=~ s/([()\\])/\\$1/g;
$val
=~ s/\n/\\n/g;
$val
=~ s/\r/\\r/g;
$val
=~ s/\t/\\t/g;
$val
=~ s/([\x00-\x1f\x7f\xff])/
sprintf
(
"\\%.3o"
,
ord
($1))/ge;
$val
=
"($val)"
;
}
my
$line
=
"%%$tag: $val"
;
my
$n
;
for
(
$n
=254;
length
(
$line
)>
$n
;
$n
+=254+
length
($/)) {
substr
(
$line
,
$n
, 0) =
"$/%%+"
;
}
return
$line
. $/;
}
sub
WriteNewTags($$$)
{
my
(
$et
,
$outfile
,
$newTags
) =
@_
;
my
$success
= 1;
my
$tag
;
my
$xmpHint
=
$$newTags
{XMP_HINT};
delete
$$newTags
{XMP_HINT};
foreach
$tag
(
sort
keys
%$newTags
) {
my
$tagInfo
=
$$newTags
{
$tag
};
my
$nvHash
=
$et
->GetNewValueHash(
$tagInfo
);
next
unless
$$nvHash
{IsCreating};
my
$val
=
$et
->GetNewValue(
$nvHash
);
$et
->VerboseValue(
"+ PostScript:$$tagInfo{Name}"
,
$val
);
Write(
$outfile
, EncodeTag(
$tag
,
$val
)) or
$success
= 0;
++
$$et
{CHANGED};
}
Write(
$outfile
,
"%ADO_ContainsXMP: MainFirst$/"
) or
$success
= 0
if
$xmpHint
;
%$newTags
= ();
return
$success
;
}
sub
CheckPSEnd($$$)
{
my
$pos
=
$_
[0]->Tell();
if
(
$pos
>=
$_
[1]) {
$_
[0]->Seek(0, 2);
$_
[2] =
substr
(
$_
[2], 0,
length
(
$_
[2]) -
$pos
+
$_
[1])
if
$pos
>
$_
[1];
}
}
sub
SplitLine($$)
{
my
(
$dataPt
,
$lines
) =
@_
;
for
(;;) {
my
$endl
;
$endl
=
pos
(
$$dataPt
),
pos
(
$$dataPt
) = 0
if
$$dataPt
=~ /\x0a/g;
if
(
$$dataPt
=~ /\x0d/g) {
if
(
defined
$endl
) {
$endl
=
pos
(
$$dataPt
)
if
pos
(
$$dataPt
) <
$endl
- 1;
}
else
{
$endl
=
pos
(
$$dataPt
);
}
}
elsif
(not
defined
$endl
) {
push
@$lines
,
$$dataPt
;
last
;
}
if
(
length
$$dataPt
==
$endl
) {
push
@$lines
,
$$dataPt
;
last
;
}
else
{
push
@$lines
,
substr
(
$$dataPt
, 0,
$endl
);
$$dataPt
=
substr
(
$$dataPt
,
$endl
);
}
}
$$dataPt
=
shift
@$lines
;
}
sub
WritePS($$)
{
my
(
$et
,
$dirInfo
) =
@_
;
$et
or
return
1;
my
$tagTablePtr
= Image::ExifTool::GetTagTable(
'Image::ExifTool::PostScript::Main'
);
my
$raf
=
$$dirInfo
{RAF};
my
$outfile
=
$$dirInfo
{OutFile};
my
$verbose
=
$et
->Options(
'Verbose'
);
my
$out
=
$et
->Options(
'TextOut'
);
my
(
$data
,
$buff
,
%flags
,
$err
,
$mode
,
$endToken
);
my
(
$dos
,
$psStart
,
$psEnd
,
$psNewStart
,
$xmpHint
);
$raf
->Read(
$data
, 4) == 4 or
return
0;
return
0
unless
$data
=~ /^(%!PS|%!Ad|\xc5\xd0\xd3\xc6)/;
if
(
$data
=~ /^%!Ad/) {
return
0
unless
$raf
->Read(
$buff
, 6) == 6 and
$buff
eq
"obe-PS"
;
$data
.=
$buff
;
}
elsif
(
$data
=~ /^\xc5\xd0\xd3\xc6/) {
$raf
->Read(
$dos
, 26) == 26 or
return
0;
$dos
=
$data
.
$dos
;
SetByteOrder(
'II'
);
$psStart
= Get32u(\
$dos
, 4);
unless
(
$raf
->Seek(
$psStart
, 0) and
$raf
->Read(
$data
, 4) == 4 and
$data
eq
'%!PS'
)
{
$et
->Error(
'Invalid PS header'
);
return
1;
}
$psEnd
=
$psStart
+ Get32u(\
$dos
, 8);
my
$base
= Get32u(\
$dos
, 20);
Set16u(0xffff, \
$dos
, 28);
if
(
$base
) {
my
%dirInfo
= (
Parent
=>
'PS'
,
RAF
=>
$raf
,
Base
=>
$base
,
NoTiffEnd
=> 1,
);
$buff
=
$et
->WriteTIFF(\
%dirInfo
);
SetByteOrder(
'II'
);
if
(
$buff
) {
$buff
=
substr
(
$buff
,
$base
);
}
else
{
my
$len
= Get32u(\
$dos
, 24);
unless
(
$raf
->Seek(
$base
, 0) and
$raf
->Read(
$buff
,
$len
) ==
$len
) {
$et
->Error(
'Error reading embedded TIFF'
);
return
1;
}
$et
->Warn(
'Bad embedded TIFF'
);
}
Set32u(0, \
$dos
, 12);
Set32u(0, \
$dos
, 16);
Set32u(
length
(
$dos
), \
$dos
, 20);
Set32u(
length
(
$buff
), \
$dos
, 24);
}
elsif
((
$base
= Get32u(\
$dos
, 12)) != 0) {
my
$len
= Get32u(\
$dos
, 16);
unless
(
$raf
->Seek(
$base
, 0) and
$raf
->Read(
$buff
,
$len
) ==
$len
) {
$et
->Error(
'Error reading metafile section'
);
return
1;
}
Set32u(
length
(
$dos
), \
$dos
, 12);
}
else
{
$buff
=
''
;
}
$psNewStart
=
length
(
$dos
) +
length
(
$buff
);
Set32u(
$psNewStart
, \
$dos
, 4);
Write(
$outfile
,
$dos
,
$buff
) or
$err
= 1;
$raf
->Seek(
$psStart
+ 4, 0);
}
local
$/ = GetInputRecordSeparator(
$raf
);
unless
($/ and
$raf
->ReadLine(
$buff
)) {
$et
->Error(
'Invalid PostScript data'
);
return
1;
}
$data
.=
$buff
;
unless
(
$data
=~ /^%!PS-Adobe-3\.(\d+)\b/ and $1 < 2) {
if
(
$et
->Error(
"Document does not conform to DSC spec. Metadata may be unreadable by other apps"
, 2)) {
return
1;
}
}
my
$psRev
= $1;
Write(
$outfile
,
$data
) or
$err
= 1;
$flags
{EPS} = 1
if
$data
=~ /EPSF/;
my
$newTags
=
$et
->GetNewTagInfoHash(
$tagTablePtr
);
$et
->InitWriteDirs(\
%psMap
,
'PostScript'
);
my
$addDirs
=
$$et
{ADD_DIRS};
my
$editDirs
=
$$et
{EDIT_DIRS};
my
%doneDir
;
$xmpHint
= 1
if
$$addDirs
{XMP};
$xmpHint
= 0
if
$$et
{DEL_GROUP}{XMP};
$$newTags
{XMP_HINT} =
$xmpHint
if
$xmpHint
;
my
(
@lines
,
$changedNL
);
my
$altnl
= ($/ eq
"\x0d"
) ?
"\x0a"
:
"\x0d"
;
for
(;;) {
if
(
@lines
) {
$data
=
shift
@lines
;
}
else
{
$raf
->ReadLine(
$data
) or
last
;
$dos
and CheckPSEnd(
$raf
,
$psEnd
,
$data
);
if
(
$data
=~ /
$altnl
/) {
if
(
length
(
$data
) > 500000 and IsPC()) {
unless
(
$changedNL
) {
$changedNL
= 1;
my
$t
= $/;
$/ =
$altnl
;
$altnl
=
$t
;
$raf
->Seek(-
length
(
$data
), 1);
next
;
}
}
else
{
SplitLine(\
$data
, \
@lines
);
}
}
}
undef
$changedNL
;
if
(
$endToken
) {
if
(
$data
=~ m/^
$endToken
\s*$/is) {
undef
$endToken
;
if
(
$mode
) {
$doneDir
{
$mode
} and
$et
->Error(
"Multiple $mode directories"
, 1);
$doneDir
{
$mode
} = 1;
WritePSDirectory(
$et
,
$outfile
,
$mode
, \
$buff
, \
%flags
) or
$err
= 1;
Write(
$outfile
,
$data
) or
$err
= 1
if
$flags
{WROTE_BEGIN};
undef
$buff
;
}
else
{
Write(
$outfile
,
$data
) or
$err
= 1;
}
}
else
{
if
(not
defined
$mode
) {
if
(
$data
=~ /^<\?xpacket begin=.{7,13}W5M0MpCehiHzreSzNTczkc9d/ and
$$editDirs
{XMP})
{
$buff
=
$data
;
$mode
=
'XMP'
;
}
else
{
Write(
$outfile
,
$data
) or
$err
= 1;
}
}
elsif
(
$mode
eq
'XMP'
) {
$buff
.=
$data
;
}
else
{
$data
=~
tr
/0-9A-Fa-f//dc;
$buff
.=
pack
(
'H*'
,
$data
);
}
}
next
;
}
elsif
(
$data
=~ m{^(%{1,2})(Begin)(?!Object:)(.*?)[:\x0d\x0a]}i) {
WriteNewTags(
$et
,
$outfile
,
$newTags
) or
$err
= 1
if
%$newTags
;
undef
$xmpHint
;
my
%modeLookup
= (
_xml_code
=>
'XMP'
,
photoshop
=>
'Photoshop'
,
iccprofile
=>
'ICC_Profile'
,
);
$verbose
> 1 and
print
$out
"$2$3\n"
;
$endToken
= $1 . ($2 eq
'begin'
?
'end'
:
'End'
) . $3;
$mode
=
$modeLookup
{
lc
($3)};
if
(
$mode
and
$$editDirs
{
$mode
}) {
$buff
=
''
;
$flags
{WROTE_BEGIN} = 0;
}
else
{
undef
$mode
;
Write(
$outfile
,
$data
) or
$err
= 1;
$flags
{WROTE_BEGIN} = 1;
}
next
;
}
elsif
(
$data
=~ /^%%(?!Page:|PlateFile:|BeginObject:)(\w+): ?(.*)/s) {
my
(
$tag
,
$val
) = ($1, $2);
if
(
$tag
eq
'Creator'
and
$val
=~ /^Adobe Illustrator/) {
if
(
$$editDirs
{XMP}) {
$et
->Warn(
"Can't write XMP to PostScript-format Illustrator files"
);
$doneDir
{XMP} = 1;
}
if
(
$$newTags
{
$tag
}) {
$et
->Warn(
"Can't change Postscript:Creator of Illustrator files"
);
delete
$$newTags
{
$tag
};
}
}
if
(
$$newTags
{
$tag
}) {
my
$tagInfo
=
$$newTags
{
$tag
};
delete
$$newTags
{
$tag
};
next
unless
ref
$tagInfo
;
$val
= DecodeComment(
$val
,
$raf
, \
@lines
, \
$data
);
$val
=
join
$et
->Options(
'ListSep'
),
@$val
if
ref
$val
eq
'ARRAY'
;
my
$nvHash
=
$et
->GetNewValueHash(
$tagInfo
);
if
(
$et
->IsOverwriting(
$nvHash
,
$val
)) {
$et
->VerboseValue(
"- PostScript:$$tagInfo{Name}"
,
$val
);
$val
=
$et
->GetNewValue(
$nvHash
);
++
$$et
{CHANGED};
next
unless
defined
$val
;
$et
->VerboseValue(
"+ PostScript:$$tagInfo{Name}"
,
$val
);
$data
= EncodeTag(
$tag
,
$val
);
}
}
}
elsif
(
defined
$xmpHint
and
$data
=~ m{^
%ADO_ContainsXMP
:? ?(.+?)[\x0d\x0a]*$}s) {
if
(
$xmpHint
) {
$data
=
"%ADO_ContainsXMP: MainFirst$/"
if
$1 eq
'NoMain'
;
}
else
{
$data
=
"%ADO_ContainsXMP: NoMain$/"
;
}
delete
$$newTags
{XMP_HINT};
undef
$xmpHint
;
}
else
{
if
(
%$newTags
and (
$data
!~ /^%\S/ or
$data
=~ /^%(
%EndComments
|
%Page
:|
%PlateFile
:|
%BeginObject
:|.
*BeginLayer
)/))
{
WriteNewTags(
$et
,
$outfile
,
$newTags
) or
$err
= 1;
undef
$xmpHint
;
}
if
(
$data
=~ /^%(
%Page
:|
%PlateFile
:|
%BeginObject
:|.
*BeginLayer
)/ or
$data
!~ m{^(%.*|\s*)$}s)
{
my
$dir
;
my
$plateFile
= (
$data
=~ /^%
%PlateFile
:/);
foreach
$dir
(
qw{Photoshop ICC_Profile XMP}
) {
next
unless
$$editDirs
{
$dir
} and not
$doneDir
{
$dir
};
if
(
$plateFile
) {
$et
->Warn(
"Can only edit PostScript information DCS Plate files"
);
last
;
}
next
unless
$$addDirs
{
$dir
} or
$dir
eq
'XMP'
;
$flags
{WROTE_BEGIN} = 0;
WritePSDirectory(
$et
,
$outfile
,
$dir
,
undef
, \
%flags
) or
$err
= 1;
$doneDir
{
$dir
} = 1;
}
if
(
$flags
{TRAILER}) {
for
(;;) {
Write(
$outfile
,
$data
) or
$err
= 1;
if
(
@lines
) {
$data
=
shift
@lines
;
}
else
{
$raf
->ReadLine(
$data
) or
undef
(
$data
),
last
;
$dos
and CheckPSEnd(
$raf
,
$psEnd
,
$data
);
if
(
$data
=~ /[\x0d\x0a]%
%EOF
\b/g) {
my
$pos
=
pos
(
$data
) - 5;
push
@lines
,
substr
(
$data
,
$pos
);
$data
=
substr
(
$data
, 0,
$pos
);
}
}
last
if
$data
=~ /^%
%EOF
\b/;
}
Write(
$outfile
,
$flags
{TRAILER}) or
$err
= 1;
}
if
(
defined
$data
) {
Write(
$outfile
,
$data
) or
$err
= 1;
Write(
$outfile
,
@lines
) or
$err
= 1
if
@lines
;
while
(
$raf
->Read(
$data
, 65536)) {
$dos
and CheckPSEnd(
$raf
,
$psEnd
,
$data
);
Write(
$outfile
,
$data
) or
$err
= 1;
}
}
last
;
}
}
Write(
$outfile
,
$data
) or
$err
= 1;
}
if
(
$dos
and not
$err
) {
if
(
ref
$outfile
eq
'SCALAR'
) {
Set32u(
length
(
$$outfile
) -
$psNewStart
,
$outfile
, 8);
}
else
{
my
$pos
=
tell
$outfile
;
unless
(
seek
(
$outfile
, 8, 0) and
print
$outfile
Set32u(
$pos
-
$psNewStart
) and
seek
(
$outfile
,
$pos
, 0))
{
$et
->Error(
"Can't write DOS-style PS files in non-seekable stream"
);
$err
= 1;
}
}
}
unless
(
$err
) {
my
(
@notDone
,
$dir
);
delete
$$newTags
{XMP_HINT};
push
@notDone
,
'PostScript'
if
%$newTags
;
foreach
$dir
(
qw{Photoshop ICC_Profile XMP}
) {
push
@notDone
,
$dir
if
$$editDirs
{
$dir
} and not
$doneDir
{
$dir
} and
not
$$et
{DEL_GROUP}{
$dir
};
}
@notDone
and
$et
->Warn(
"Couldn't write "
.
join
('/
',@notDone).'
information');
}
$endToken
and
$et
->Error(
"File missing $endToken"
);
return
$err
? -1 : 1;
}
1;