#689: Make private implementation pf EpsImage really private

v0.27.3
vog 14 years ago
parent 98e90dc738
commit 3b13ab8717

@ -55,134 +55,352 @@ EXIV2_RCSID("@(#) $Id: epsimage.cpp $")
#include <sstream> #include <sstream>
#include <string> #include <string>
// signature of DOS EPS
static const std::string epsDosSignature = "\xc5\xd0\xd3\xc6";
// first line of EPS
static const std::string epsFirstLine[] = {
"%!PS-Adobe-3.0 EPSF-3.0",
"%!PS-Adobe-3.0 EPSF-3.0 ", // OpenOffice
"%!PS-Adobe-3.1 EPSF-3.0", // Illustrator
};
// blank EPS file
static const std::string epsBlank = "%!PS-Adobe-3.0 EPSF-3.0\n"
"%%BoundingBox: 0 0 0 0\n";
// list of all valid XMP headers
static const struct { std::string header; std::string charset; } xmpHeadersDef[] = {
// We do not enforce the trailing "?>" here, because the XMP specification
// permits additional attributes after begin="..." and id="...".
// normal headers
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16BE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16BE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16LE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16LE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32BE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32BE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32LE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32LE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32LE"},
// deprecated headers (empty begin attribute, UTF-8 only)
{"<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin=\"\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin='' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
};
// list of all valid XMP trailers
static const struct { std::string trailer; bool readOnly; } xmpTrailersDef[] = {
// We do not enforce the trailing "?>" here, because the XMP specification
// permits additional attributes after end="...".
{"<?xpacket end=\"r\"", true},
{"<?xpacket end='r'", true},
{"<?xpacket end=\"w\"", false},
{"<?xpacket end='w'", false},
};
// closing part of all valid XMP trailers
static const std::string xmpTrailerEndDef = "?>";
// ***************************************************************************** // *****************************************************************************
// class member definitions namespace {
namespace Exiv2
{ using namespace Exiv2;
EpsImage::EpsImage(BasicIo::AutoPtr io, bool create) // signature of DOS EPS
: Image(ImageType::eps, mdXmp, io) static const std::string epsDosSignature = "\xc5\xd0\xd3\xc6";
// first line of EPS
static const std::string epsFirstLine[] = {
"%!PS-Adobe-3.0 EPSF-3.0",
"%!PS-Adobe-3.0 EPSF-3.0 ", // OpenOffice
"%!PS-Adobe-3.1 EPSF-3.0", // Illustrator
};
// blank EPS file
static const std::string epsBlank = "%!PS-Adobe-3.0 EPSF-3.0\n"
"%%BoundingBox: 0 0 0 0\n";
// list of all valid XMP headers
static const struct { std::string header; std::string charset; } xmpHeadersDef[] = {
// We do not enforce the trailing "?>" here, because the XMP specification
// permits additional attributes after begin="..." and id="...".
// normal headers
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16BE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16BE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16LE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-16LE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-16LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32BE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32BE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32BE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32LE"},
{"<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32LE"},
{"<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-32LE"},
{"<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-32LE"},
// deprecated headers (empty begin attribute, UTF-8 only)
{"<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin=\"\" id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
{"<?xpacket begin='' id=\"W5M0MpCehiHzreSzNTczkc9d\"", "UTF-8"},
{"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'", "UTF-8"},
};
// list of all valid XMP trailers
static const struct { std::string trailer; bool readOnly; } xmpTrailersDef[] = {
// We do not enforce the trailing "?>" here, because the XMP specification
// permits additional attributes after end="...".
{"<?xpacket end=\"r\"", true},
{"<?xpacket end='r'", true},
{"<?xpacket end=\"w\"", false},
{"<?xpacket end='w'", false},
};
// closing part of all valid XMP trailers
static const std::string xmpTrailerEndDef = "?>";
//! Write data into temp file, taking care of errors
static void writeTemp(BasicIo& tempIo, const char* data, size_t size)
{ {
//LogMsg::setLevel(LogMsg::debug); if (size == 0) return;
if (create) { if (tempIo.write(reinterpret_cast<const byte*>(data), static_cast<long>(size)) != static_cast<long>(size)) {
if (io_->open() == 0) { #ifndef SUPPRESS_WARNINGS
#ifdef DEBUG EXV_WARNING << "Failed to write to temporary file.\n";
EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n"; #endif
#endif throw Error(21);
IoCloser closer(*io_);
if (io_->write(reinterpret_cast<const byte*>(epsBlank.data()), static_cast<long>(epsBlank.size())) != static_cast<long>(epsBlank.size())) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Failed to write blank EPS image.\n";
#endif
throw Error(21);
}
}
} }
} }
std::string EpsImage::mimeType() const //! Write data into temp file, taking care of errors
static void writeTemp(BasicIo& tempIo, const std::string &data)
{ {
return "application/postscript"; writeTemp(tempIo, data.data(), data.size());
} }
void EpsImage::setComment(const std::string& /*comment*/) //! Check whether a string has a certain beginning
static bool startsWith(const std::string& s, const std::string& start)
{ {
throw Error(32, "Image comment", "EPS"); return s.size() >= start.size() && memcmp(s.data(), start.data(), start.size()) == 0;
} }
void EpsImage::readMetadata() //! Check whether a string contains only white space characters
static bool onlyWhitespaces(const std::string& s)
{ {
doReadWriteMetadata(/* write = */ false); // According to the DSC 3.0 specification, 4.4 Parsing Rules,
// only spaces and tabs are considered to be white space characters.
return s.find_first_not_of(" \t") == std::string::npos;
} }
void EpsImage::writeMetadata() //! Convert an integer of type size_t to a decimal string
static std::string toString(size_t size)
{
std::ostringstream stream;
stream << size;
return stream.str();
}
//! Read the next line of a buffer, allow for changing line ending style
static size_t readLine(std::string& line, const char* data, size_t startPos, size_t size)
{
line.clear();
size_t pos = startPos;
// step through line
while (pos < size && data[pos] != '\r' && data[pos] != '\n') {
line += data[pos];
pos++;
}
// skip line ending, if present
if (pos >= size) return pos;
pos++;
if (pos >= size) return pos;
if (data[pos - 1] == '\r' && data[pos] == '\n') pos++;
return pos;
}
//! Read the previous line of a buffer, allow for changing line ending style
static size_t readPrevLine(std::string& line, const char* data, size_t startPos, size_t size)
{
line.clear();
size_t pos = startPos;
if (pos > size) return pos;
// skip line ending of previous line, if present
if (pos <= 0) return pos;
if (data[pos - 1] == '\r' || data[pos - 1] == '\n') {
pos--;
if (pos <= 0) return pos;
if (data[pos - 1] == '\r' && data[pos] == '\n') {
pos--;
if (pos <= 0) return pos;
}
}
// step through previous line
while (pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') {
pos--;
line += data[pos];
}
std::reverse(line.begin(), line.end());
return pos;
}
//! Find an XMP block
static void findXmp(size_t& xmpPos, size_t& xmpSize, const char* data, size_t size, bool write)
{ {
doReadWriteMetadata(/* write = */ true); // prepare list of valid XMP headers
std::vector<std::pair<std::string, std::string> > xmpHeaders;
for (size_t i = 0; i < (sizeof xmpHeadersDef) / (sizeof *xmpHeadersDef); i++) {
const std::string &charset = xmpHeadersDef[i].charset;
std::string header(xmpHeadersDef[i].header);
if (!convertStringCharset(header, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
xmpHeaders.push_back(make_pair(header, charset));
}
// search for valid XMP header
xmpSize = 0;
for (xmpPos = 0; xmpPos < size; xmpPos++) {
if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
for (size_t i = 0; i < xmpHeaders.size(); i++) {
const std::string &header = xmpHeaders[i].first;
if (xmpPos + header.size() > size) continue;
if (memcmp(data + xmpPos, header.data(), header.size()) != 0) continue;
#ifdef DEBUG
EXV_DEBUG << "findXmp: Found XMP header at position: " << xmpPos << "\n";
#endif
// prepare list of valid XMP trailers in the charset of the header
const std::string &charset = xmpHeaders[i].second;
std::vector<std::pair<std::string, bool> > xmpTrailers;
for (size_t j = 0; j < (sizeof xmpTrailersDef) / (sizeof *xmpTrailersDef); j++) {
std::string trailer(xmpTrailersDef[j].trailer);
if (!convertStringCharset(trailer, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
xmpTrailers.push_back(make_pair(trailer, xmpTrailersDef[j].readOnly));
}
std::string xmpTrailerEnd(xmpTrailerEndDef);
if (!convertStringCharset(xmpTrailerEnd, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
// search for valid XMP trailer
for (size_t trailerPos = xmpPos + header.size(); trailerPos < size; trailerPos++) {
if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
for (size_t j = 0; j < xmpTrailers.size(); j++) {
const std::string &trailer = xmpTrailers[j].first;
if (trailerPos + trailer.size() > size) continue;
if (memcmp(data + trailerPos, trailer.data(), trailer.size()) != 0) continue;
#ifdef DEBUG
EXV_DEBUG << "findXmp: Found XMP trailer at position: " << trailerPos << "\n";
#endif
const bool readOnly = xmpTrailers[j].second;
if (readOnly) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unable to handle read-only XMP metadata yet. Please provide your"
" sample EPS file to the Exiv2 project: http://dev.exiv2.org/projects/exiv2\n";
#endif
throw Error(write ? 21 : 14);
}
// search for end of XMP trailer
for (size_t trailerEndPos = trailerPos + trailer.size(); trailerEndPos + xmpTrailerEnd.size() <= size; trailerEndPos++) {
if (memcmp(data + trailerEndPos, xmpTrailerEnd.data(), xmpTrailerEnd.size()) == 0) {
xmpSize = (trailerEndPos + xmpTrailerEnd.size()) - xmpPos;
return;
}
}
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Found XMP header but incomplete XMP trailer.\n";
#endif
throw Error(write ? 21 : 14);
}
}
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Found XMP header but no XMP trailer.\n";
#endif
throw Error(write ? 21 : 14);
}
}
} }
void EpsImage::doReadWriteMetadata(bool write) //! Find removable XMP embeddings
static std::vector<std::pair<size_t, size_t> > findRemovableEmbeddings(const char* data, size_t posEof, size_t posEndPageSetup,
size_t xmpPos, size_t xmpSize, bool write)
{ {
std::vector<std::pair<size_t, size_t> > removableEmbeddings;
std::string line;
size_t pos;
// check after XMP
pos = xmpPos + xmpSize;
pos = readLine(line, data, pos, posEof);
if (line != "") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found empty line after XMP\n";
#endif
pos = readLine(line, data, pos, posEof);
if (line != "%end_xml_packet") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found %end_xml_packet\n";
#endif
size_t posEmbeddingEnd = 0;
for (int i = 0; i < 32; i++) {
pos = readLine(line, data, pos, posEof);
if (line == "%end_xml_code") {
posEmbeddingEnd = pos;
break;
}
}
if (posEmbeddingEnd == 0) return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found %end_xml_code\n";
#endif
// check before XMP
pos = xmpPos;
pos = readPrevLine(line, data, pos, posEof);
if (!startsWith(line, "%begin_xml_packet: ")) return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found %begin_xml_packet: ...\n";
#endif
size_t posEmbeddingStart = posEof;
for (int i = 0; i < 32; i++) {
pos = readPrevLine(line, data, pos, posEof);
if (line == "%begin_xml_code") {
posEmbeddingStart = pos;
break;
}
}
if (posEmbeddingStart == posEof) return removableEmbeddings;
#ifdef DEBUG #ifdef DEBUG
if (write) { EXV_DEBUG << "findRemovableEmbeddings: Found %begin_xml_code\n";
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Writing EPS file " << io_->path() << "\n"; #endif
// check at EOF
pos = posEof;
pos = readPrevLine(line, data, pos, posEof);
if (line == "[/EMC pdfmark") {
// Exiftool style
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found [/EMC pdfmark\n";
#endif
} else if (line == "[/NamespacePop pdfmark") {
// Photoshop style
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found /NamespacePop pdfmark\n";
#endif
pos = readPrevLine(line, data, pos, posEof);
if (line != "[{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT pdfmark") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "findRemovableEmbeddings: Found /PUT pdfmark\n";
#endif
} else { } else {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Reading EPS file " << io_->path() << "\n"; return removableEmbeddings;
} }
// check whether another XMP metadata block would take precedence if this one was removed
{
size_t xmpPos, xmpSize;
findXmp(xmpPos, xmpSize, data, posEndPageSetup, write);
if (xmpSize != 0) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Second XMP metadata block interferes at position: " << xmpPos << "\n";
#endif
if (write) throw Error(21);
}
}
removableEmbeddings.push_back(std::make_pair(posEmbeddingStart, posEmbeddingEnd));
removableEmbeddings.push_back(std::make_pair(pos, posEof));
#ifdef DEBUG
const size_t n = removableEmbeddings.size();
EXV_DEBUG << "findRemovableEmbeddings: Recognized Photoshop-style XMP embedding at "
"[" << removableEmbeddings[n-2].first << "," << removableEmbeddings[n-2].second << ")"
" with trailer "
"[" << removableEmbeddings[n-1].first << "," << removableEmbeddings[n-1].second << ")"
"\n";
#endif #endif
return removableEmbeddings;
}
//! Unified implementation of reading and writing EPS metadata
static void readWriteEpsMetadata(BasicIo& io, std::string& xmpPacket, bool write)
{
// open input file // open input file
if (io_->open() != 0) { if (io.open() != 0) {
throw Error(9, io_->path(), strError()); throw Error(9, io.path(), strError());
} }
IoCloser closer(*io_); IoCloser closer(io);
// read from input file via memory map // read from input file via memory map
const char *data = reinterpret_cast<const char*>(io_->mmap()); const char *data = reinterpret_cast<const char*>(io.mmap());
const size_t size = io_->size(); const size_t size = io.size();
size_t pos = 0; size_t pos = 0;
std::string line; std::string line;
@ -193,7 +411,7 @@ namespace Exiv2
pos = readLine(line, data, firstLinePos, size); pos = readLine(line, data, firstLinePos, size);
const std::string firstLine = line; const std::string firstLine = line;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: First line: " << firstLine << "\n"; EXV_DEBUG << "readWriteEpsMetadata: First line: " << firstLine << "\n";
#endif #endif
bool matched = false; bool matched = false;
for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) {
@ -213,13 +431,13 @@ namespace Exiv2
const std::string lineEnding(data + firstLinePos + firstLine.size(), pos - (firstLinePos + firstLine.size())); const std::string lineEnding(data + firstLinePos + firstLine.size(), pos - (firstLinePos + firstLine.size()));
#ifdef DEBUG #ifdef DEBUG
if (lineEnding == "\n") { if (lineEnding == "\n") {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: Unix (LF)\n"; EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Unix (LF)\n";
} else if (lineEnding == "\r") { } else if (lineEnding == "\r") {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: Mac (CR)\n"; EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Mac (CR)\n";
} else if (lineEnding == "\r\n") { } else if (lineEnding == "\r\n") {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: DOS (CR LF)\n"; EXV_DEBUG << "readWriteEpsMetadata: Line ending style: DOS (CR LF)\n";
} else { } else {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: (unknown)\n"; EXV_DEBUG << "readWriteEpsMetadata: Line ending style: (unknown)\n";
} }
#endif #endif
@ -246,7 +464,7 @@ namespace Exiv2
if (posEndComments == size) { if (posEndComments == size) {
posEndComments = startPos; posEndComments = startPos;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit EndComments at position: " << startPos << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndComments at position: " << startPos << "\n";
#endif #endif
} }
} }
@ -255,13 +473,13 @@ namespace Exiv2
posPage = startPos; posPage = startPos;
implicitPage = true; implicitPage = true;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit Page at position: " << startPos << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Found implicit Page at position: " << startPos << "\n";
#endif #endif
} }
if (posEndPageSetup == size && posPage != size && !inPageSetup) { if (posEndPageSetup == size && posPage != size && !inPageSetup) {
posEndPageSetup = startPos; posEndPageSetup = startPos;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit EndPageSetup at position: " << startPos << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n";
#endif #endif
} }
} }
@ -269,7 +487,7 @@ namespace Exiv2
if (line == "%%EOF" && posPageTrailer == size) { if (line == "%%EOF" && posPageTrailer == size) {
posPageTrailer = startPos; posPageTrailer = startPos;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit PageTrailer at position: " << startPos << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Found implicit PageTrailer at position: " << startPos << "\n";
#endif #endif
} }
// explicit comments // explicit comments
@ -349,7 +567,7 @@ namespace Exiv2
} }
#ifdef DEBUG #ifdef DEBUG
if (significantLine) { if (significantLine) {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n";
} }
#endif #endif
} }
@ -392,12 +610,12 @@ namespace Exiv2
readLine(line, data, posLineAfterXmp, size); readLine(line, data, posLineAfterXmp, size);
if (line == "% &&end XMP packet marker&&" || line == "% &&end XMP packet marker&&") { if (line == "% &&end XMP packet marker&&" || line == "% &&end XMP packet marker&&") {
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Recognized flexible XMP embedding\n"; EXV_DEBUG << "readWriteEpsMetadata: Recognized flexible XMP embedding\n";
#endif #endif
const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, size); const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, size);
if (startsWith(line, "%begin_xml_packet:")) { if (startsWith(line, "%begin_xml_packet:")) {
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found %begin_xml_packet before flexible XMP embedding\n"; EXV_DEBUG << "readWriteEpsMetadata: Found %begin_xml_packet before flexible XMP embedding\n";
#endif #endif
if (write) { if (write) {
fixBeginXmlPacket = true; fixBeginXmlPacket = true;
@ -422,28 +640,14 @@ namespace Exiv2
} }
if (!write) { if (!write) {
// copy and decode XMP metadata // copy
xmpPacket_.assign(data + xmpPos, xmpSize); xmpPacket.assign(data + xmpPos, xmpSize);
if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_) > 1) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Failed to decode XMP metadata.\n";
#endif
throw Error(14);
}
} else { } else {
const bool useExistingEmbedding = (xmpPos != size && removableEmbeddings.empty()); const bool useExistingEmbedding = (xmpPos != size && removableEmbeddings.empty());
// encode XMP metadata if necessary
if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Failed to encode XMP metadata.\n";
#endif
throw Error(21);
}
// TODO: Add support for deleting XMP metadata. Note that this is not // TODO: Add support for deleting XMP metadata. Note that this is not
// as simple as it may seem, and requires special attention! // as simple as it may seem, and requires special attention!
if (xmpPacket_.size() == 0) { if (xmpPacket.size() == 0) {
#ifndef SUPPRESS_WARNINGS #ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Deleting XMP metadata is currently not supported.\n"; EXV_WARNING << "Deleting XMP metadata is currently not supported.\n";
#endif #endif
@ -451,7 +655,7 @@ namespace Exiv2
} }
// create temporary output file // create temporary output file
BasicIo::AutoPtr tempIo(io_->temporary()); BasicIo::AutoPtr tempIo(io.temporary());
assert (tempIo.get() != 0); assert (tempIo.get() != 0);
if (!tempIo->isopen()) { if (!tempIo->isopen()) {
#ifndef SUPPRESS_WARNINGS #ifndef SUPPRESS_WARNINGS
@ -460,7 +664,7 @@ namespace Exiv2
throw Error(21); throw Error(21);
} }
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Created temporary file " << tempIo->path() << "\n"; EXV_DEBUG << "readWriteEpsMetadata: Created temporary file " << tempIo->path() << "\n";
#endif #endif
// sort all positions // sort all positions
@ -504,7 +708,7 @@ namespace Exiv2
if (pos == size && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') { if (pos == size && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') {
writeTemp(*tempIo, lineEnding); writeTemp(*tempIo, lineEnding);
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Added missing line ending of last line\n"; EXV_DEBUG << "readWriteEpsMetadata: Added missing line ending of last line\n";
#endif #endif
} }
// update and complement DSC comments // update and complement DSC comments
@ -566,9 +770,9 @@ namespace Exiv2
// insert XMP metadata into existing flexible embedding // insert XMP metadata into existing flexible embedding
if (pos == xmpPos) { if (pos == xmpPos) {
if (fixBeginXmlPacket) { if (fixBeginXmlPacket) {
writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket_.size()) + lineEnding); writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding);
} }
writeTemp(*tempIo, xmpPacket_.data(), xmpPacket_.size()); writeTemp(*tempIo, xmpPacket.data(), xmpPacket.size());
skipPos += xmpSize; skipPos += xmpSize;
} }
} else { } else {
@ -598,9 +802,9 @@ namespace Exiv2
if (photoshop) { if (photoshop) {
writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop. " writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop. "
"Parameter must be exact size of XMP metadata." + lineEnding); "Parameter must be exact size of XMP metadata." + lineEnding);
writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket_.size()) + lineEnding); writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding);
} }
writeTemp(*tempIo, xmpPacket_.data(), xmpPacket_.size()); writeTemp(*tempIo, xmpPacket.data(), xmpPacket.size());
writeTemp(*tempIo, lineEnding); writeTemp(*tempIo, lineEnding);
writeTemp(*tempIo, "% &&end XMP packet marker&&" + lineEnding); writeTemp(*tempIo, "% &&end XMP packet marker&&" + lineEnding);
writeTemp(*tempIo, "[/Document 1 dict begin" + lineEnding); writeTemp(*tempIo, "[/Document 1 dict begin" + lineEnding);
@ -635,273 +839,90 @@ namespace Exiv2
} }
// copy temporary file to real output file // copy temporary file to real output file
io_->close(); io.close();
io_->transfer(*tempIo); io.transfer(*tempIo);
}
#ifdef DEBUG
if (write) {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Finished writing EPS file " << io_->path() << "\n";
} else {
EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Finished reading EPS file " << io_->path() << "\n";
} }
#endif
} }
std::vector<std::pair<size_t, size_t> > EpsImage::findRemovableEmbeddings(const char* data, size_t posEof, size_t posEndPageSetup, } // namespace
size_t xmpPos, size_t xmpSize, bool write)
{
std::vector<std::pair<size_t, size_t> > removableEmbeddings;
std::string line;
size_t pos;
// check after XMP
pos = xmpPos + xmpSize;
pos = readLine(line, data, pos, posEof);
if (line != "") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found empty line after XMP\n";
#endif
pos = readLine(line, data, pos, posEof);
if (line != "%end_xml_packet") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found %end_xml_packet\n";
#endif
size_t posEmbeddingEnd = 0;
for (int i = 0; i < 32; i++) {
pos = readLine(line, data, pos, posEof);
if (line == "%end_xml_code") {
posEmbeddingEnd = pos;
break;
}
}
if (posEmbeddingEnd == 0) return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found %end_xml_code\n";
#endif
// check before XMP
pos = xmpPos;
pos = readPrevLine(line, data, pos, posEof);
if (!startsWith(line, "%begin_xml_packet: ")) return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found %begin_xml_packet: ...\n";
#endif
size_t posEmbeddingStart = posEof;
for (int i = 0; i < 32; i++) {
pos = readPrevLine(line, data, pos, posEof);
if (line == "%begin_xml_code") {
posEmbeddingStart = pos;
break;
}
}
if (posEmbeddingStart == posEof) return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found %begin_xml_code\n";
#endif
// check at EOF
pos = posEof;
pos = readPrevLine(line, data, pos, posEof);
if (line == "[/EMC pdfmark") {
// Exiftool style
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found [/EMC pdfmark\n";
#endif
} else if (line == "[/NamespacePop pdfmark") {
// Photoshop style
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found /NamespacePop pdfmark\n";
#endif
pos = readPrevLine(line, data, pos, posEof);
if (line != "[{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT pdfmark") return removableEmbeddings;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Found /PUT pdfmark\n";
#endif
} else {
return removableEmbeddings;
}
// check whether another XMP metadata block would take precedence if this one was removed
{
size_t xmpPos, xmpSize;
findXmp(xmpPos, xmpSize, data, posEndPageSetup, write);
if (xmpSize != 0) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Second XMP metadata block interferes at position: " << xmpPos << "\n";
#endif
if (write) throw Error(21);
}
}
removableEmbeddings.push_back(std::make_pair(posEmbeddingStart, posEmbeddingEnd)); // *****************************************************************************
removableEmbeddings.push_back(std::make_pair(pos, posEof)); // class member definitions
#ifdef DEBUG namespace Exiv2
const size_t n = removableEmbeddings.size(); {
EXV_DEBUG << "Exiv2::EpsImage::findRemovableEmbeddings: Recognized Photoshop-style XMP embedding at "
"[" << removableEmbeddings[n-2].first << "," << removableEmbeddings[n-2].second << ")"
" with trailer "
"[" << removableEmbeddings[n-1].first << "," << removableEmbeddings[n-1].second << ")"
"\n";
#endif
return removableEmbeddings;
}
void EpsImage::findXmp(size_t& xmpPos, size_t& xmpSize, const char* data, size_t size, bool write) EpsImage::EpsImage(BasicIo::AutoPtr io, bool create)
: Image(ImageType::eps, mdXmp, io)
{ {
// prepare list of valid XMP headers //LogMsg::setLevel(LogMsg::debug);
std::vector<std::pair<std::string, std::string> > xmpHeaders; if (create) {
for (size_t i = 0; i < (sizeof xmpHeadersDef) / (sizeof *xmpHeadersDef); i++) { if (io_->open() == 0) {
const std::string &charset = xmpHeadersDef[i].charset;
std::string header(xmpHeadersDef[i].header);
if (!convertStringCharset(header, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
xmpHeaders.push_back(make_pair(header, charset));
}
// search for valid XMP header
xmpSize = 0;
for (xmpPos = 0; xmpPos < size; xmpPos++) {
if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
for (size_t i = 0; i < xmpHeaders.size(); i++) {
const std::string &header = xmpHeaders[i].first;
if (xmpPos + header.size() > size) continue;
if (memcmp(data + xmpPos, header.data(), header.size()) != 0) continue;
#ifdef DEBUG #ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findXmp: Found XMP header at position: " << xmpPos << "\n"; EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n";
#endif #endif
IoCloser closer(*io_);
// prepare list of valid XMP trailers in the charset of the header if (io_->write(reinterpret_cast<const byte*>(epsBlank.data()), static_cast<long>(epsBlank.size())) != static_cast<long>(epsBlank.size())) {
const std::string &charset = xmpHeaders[i].second; #ifndef SUPPRESS_WARNINGS
std::vector<std::pair<std::string, bool> > xmpTrailers; EXV_WARNING << "Failed to write blank EPS image.\n";
for (size_t j = 0; j < (sizeof xmpTrailersDef) / (sizeof *xmpTrailersDef); j++) { #endif
std::string trailer(xmpTrailersDef[j].trailer); throw Error(21);
if (!convertStringCharset(trailer, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
xmpTrailers.push_back(make_pair(trailer, xmpTrailersDef[j].readOnly));
}
std::string xmpTrailerEnd(xmpTrailerEndDef);
if (!convertStringCharset(xmpTrailerEnd, "UTF-8", charset.c_str())) {
throw Error(28, charset);
}
// search for valid XMP trailer
for (size_t trailerPos = xmpPos + header.size(); trailerPos < size; trailerPos++) {
if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue;
for (size_t j = 0; j < xmpTrailers.size(); j++) {
const std::string &trailer = xmpTrailers[j].first;
if (trailerPos + trailer.size() > size) continue;
if (memcmp(data + trailerPos, trailer.data(), trailer.size()) != 0) continue;
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::findXmp: Found XMP trailer at position: " << trailerPos << "\n";
#endif
const bool readOnly = xmpTrailers[j].second;
if (readOnly) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Unable to handle read-only XMP metadata yet. Please provide your"
" sample EPS file to the Exiv2 project: http://dev.exiv2.org/projects/exiv2\n";
#endif
throw Error(write ? 21 : 14);
}
// search for end of XMP trailer
for (size_t trailerEndPos = trailerPos + trailer.size(); trailerEndPos + xmpTrailerEnd.size() <= size; trailerEndPos++) {
if (memcmp(data + trailerEndPos, xmpTrailerEnd.data(), xmpTrailerEnd.size()) == 0) {
xmpSize = (trailerEndPos + xmpTrailerEnd.size()) - xmpPos;
return;
}
}
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Found XMP header but incomplete XMP trailer.\n";
#endif
throw Error(write ? 21 : 14);
}
} }
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Found XMP header but no XMP trailer.\n";
#endif
throw Error(write ? 21 : 14);
} }
} }
} }
size_t EpsImage::readLine(std::string& line, const char* data, size_t startPos, size_t size) std::string EpsImage::mimeType() const
{ {
line.clear(); return "application/postscript";
size_t pos = startPos;
// step through line
while (pos < size && data[pos] != '\r' && data[pos] != '\n') {
line += data[pos];
pos++;
}
// skip line ending, if present
if (pos >= size) return pos;
pos++;
if (pos >= size) return pos;
if (data[pos - 1] == '\r' && data[pos] == '\n') pos++;
return pos;
} }
size_t EpsImage::readPrevLine(std::string& line, const char* data, size_t startPos, size_t size) void EpsImage::setComment(const std::string& /*comment*/)
{ {
line.clear(); throw Error(32, "Image comment", "EPS");
size_t pos = startPos;
if (pos > size) return pos;
// skip line ending of previous line, if present
if (pos <= 0) return pos;
if (data[pos - 1] == '\r' || data[pos - 1] == '\n') {
pos--;
if (pos <= 0) return pos;
if (data[pos - 1] == '\r' && data[pos] == '\n') {
pos--;
if (pos <= 0) return pos;
}
}
// step through previous line
while (pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') {
pos--;
line += data[pos];
}
std::reverse(line.begin(), line.end());
return pos;
} }
bool EpsImage::startsWith(const std::string& s, const std::string& start) void EpsImage::readMetadata()
{ {
return s.size() >= start.size() && memcmp(s.data(), start.data(), start.size()) == 0; #ifdef DEBUG
} EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Reading EPS file " << io_->path() << "\n";
#endif
bool EpsImage::onlyWhitespaces(const std::string& s) // read metadata
{ readWriteEpsMetadata(*io_, xmpPacket_, /* write = */ false);
// According to the DSC 3.0 specification, 4.4 Parsing Rules,
// only spaces and tabs are considered to be white space characters.
return s.find_first_not_of(" \t") == std::string::npos;
}
std::string EpsImage::toString(size_t size) // decode XMP metadata
{ if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_) > 1) {
std::ostringstream stream; #ifndef SUPPRESS_WARNINGS
stream << size; EXV_WARNING << "Failed to decode XMP metadata.\n";
return stream.str(); #endif
throw Error(14);
}
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Finished reading EPS file " << io_->path() << "\n";
#endif
} }
void EpsImage::writeTemp(BasicIo& tempIo, const char* data, size_t size) void EpsImage::writeMetadata()
{ {
if (size == 0) return; #ifdef DEBUG
if (tempIo.write(reinterpret_cast<const byte*>(data), static_cast<long>(size)) != static_cast<long>(size)) { EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Writing EPS file " << io_->path() << "\n";
#endif
// encode XMP metadata if necessary
if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) {
#ifndef SUPPRESS_WARNINGS #ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Failed to write to temporary file.\n"; EXV_WARNING << "Failed to encode XMP metadata.\n";
#endif #endif
throw Error(21); throw Error(21);
} }
}
void EpsImage::writeTemp(BasicIo& tempIo, const std::string &data) // write metadata
{ readWriteEpsMetadata(*io_, xmpPacket_, /* write = */ true);
writeTemp(tempIo, data.data(), data.size());
#ifdef DEBUG
EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Finished writing EPS file " << io_->path() << "\n";
#endif
} }
// ************************************************************************* // *************************************************************************

@ -105,31 +105,6 @@ namespace Exiv2
EpsImage& operator=(const EpsImage& rhs); EpsImage& operator=(const EpsImage& rhs);
//@} //@}
//! @name Internal implementation
//@{
//! Unified implementation of reading and writing metadata
EXV_DLLLOCAL void doReadWriteMetadata(bool write);
//! Find removable XMP embeddings
EXV_DLLLOCAL static std::vector<std::pair<size_t, size_t> > findRemovableEmbeddings(const char* data, size_t posEof, size_t posEndPageSetup,
size_t xmpPos, size_t xmpSize, bool write);
//! Find an XMP block
EXV_DLLLOCAL static void findXmp(size_t& xmpPos, size_t& xmpSize, const char* data, size_t size, bool write);
//! Read the next line of a buffer, allow for changing line ending style
EXV_DLLLOCAL static size_t readLine(std::string& line, const char* data, size_t startPos, size_t size);
//! Read the previous line of a buffer, allow for changing line ending style
EXV_DLLLOCAL static size_t readPrevLine(std::string& line, const char* data, size_t startPos, size_t size);
//! Check whether a string has a certain beginning
EXV_DLLLOCAL static bool startsWith(const std::string& s, const std::string& start);
//! Check whether a string contains only white space characters
EXV_DLLLOCAL static bool onlyWhitespaces(const std::string& s);
//! Convert an integer of type size_t to a decimal string
EXV_DLLLOCAL static std::string toString(size_t size);
//! Write data into temp file, taking care of errors
EXV_DLLLOCAL static void writeTemp(BasicIo& tempIo, const char* data, size_t size);
//! Write data into temp file, taking care of errors
EXV_DLLLOCAL static void writeTemp(BasicIo& tempIo, const std::string &data);
//@}
}; // class EpsImage }; // class EpsImage
// ***************************************************************************** // *****************************************************************************

Loading…
Cancel
Save