diff --git a/src/epsimage.cpp b/src/epsimage.cpp index a67e3b7b..66988755 100644 --- a/src/epsimage.cpp +++ b/src/epsimage.cpp @@ -55,134 +55,352 @@ EXIV2_RCSID("@(#) $Id: epsimage.cpp $") #include #include -// 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 - {"" here, because the XMP specification - // permits additional attributes after end="...". - - {""; - // ***************************************************************************** -// class member definitions -namespace Exiv2 -{ - - EpsImage::EpsImage(BasicIo::AutoPtr io, bool create) - : Image(ImageType::eps, mdXmp, io) +namespace { + + using namespace Exiv2; + + // 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 + {"" here, because the XMP specification + // permits additional attributes after end="...". + + {""; + + //! 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 (create) { - if (io_->open() == 0) { - #ifdef DEBUG - EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n"; - #endif - IoCloser closer(*io_); - if (io_->write(reinterpret_cast(epsBlank.data()), static_cast(epsBlank.size())) != static_cast(epsBlank.size())) { - #ifndef SUPPRESS_WARNINGS - EXV_WARNING << "Failed to write blank EPS image.\n"; - #endif - throw Error(21); - } - } + if (size == 0) return; + if (tempIo.write(reinterpret_cast(data), static_cast(size)) != static_cast(size)) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Failed to write to temporary file.\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 > 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 > 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 > findRemovableEmbeddings(const char* data, size_t posEof, size_t posEndPageSetup, + size_t xmpPos, size_t xmpSize, bool write) { + std::vector > 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 - if (write) { - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Writing EPS file " << io_->path() << "\n"; + EXV_DEBUG << "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 << "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 { - 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 + return removableEmbeddings; + } + //! Unified implementation of reading and writing EPS metadata + static void readWriteEpsMetadata(BasicIo& io, std::string& xmpPacket, bool write) + { // open input file - if (io_->open() != 0) { - throw Error(9, io_->path(), strError()); + if (io.open() != 0) { + throw Error(9, io.path(), strError()); } - IoCloser closer(*io_); + IoCloser closer(io); // read from input file via memory map - const char *data = reinterpret_cast(io_->mmap()); - const size_t size = io_->size(); + const char *data = reinterpret_cast(io.mmap()); + const size_t size = io.size(); size_t pos = 0; std::string line; @@ -193,7 +411,7 @@ namespace Exiv2 pos = readLine(line, data, firstLinePos, size); const std::string firstLine = line; #ifdef DEBUG - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: First line: " << firstLine << "\n"; + EXV_DEBUG << "readWriteEpsMetadata: First line: " << firstLine << "\n"; #endif bool matched = false; 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())); #ifdef DEBUG 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") { - 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") { - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: DOS (CR LF)\n"; + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: DOS (CR LF)\n"; } else { - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: (unknown)\n"; + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: (unknown)\n"; } #endif @@ -246,7 +464,7 @@ namespace Exiv2 if (posEndComments == size) { posEndComments = startPos; #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 } } @@ -255,13 +473,13 @@ namespace Exiv2 posPage = startPos; implicitPage = true; #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 } if (posEndPageSetup == size && posPage != size && !inPageSetup) { posEndPageSetup = startPos; #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 } } @@ -269,7 +487,7 @@ namespace Exiv2 if (line == "%%EOF" && posPageTrailer == size) { posPageTrailer = startPos; #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 } // explicit comments @@ -349,7 +567,7 @@ namespace Exiv2 } #ifdef DEBUG 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 } @@ -392,12 +610,12 @@ namespace Exiv2 readLine(line, data, posLineAfterXmp, size); if (line == "% &&end XMP packet marker&&" || line == "% &&end XMP packet marker&&") { #ifdef DEBUG - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Recognized flexible XMP embedding\n"; + EXV_DEBUG << "readWriteEpsMetadata: Recognized flexible XMP embedding\n"; #endif const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, size); if (startsWith(line, "%begin_xml_packet:")) { #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 if (write) { fixBeginXmlPacket = true; @@ -422,28 +640,14 @@ namespace Exiv2 } if (!write) { - // copy and decode XMP metadata - 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); - } + // copy + xmpPacket.assign(data + xmpPos, xmpSize); } else { 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 // as simple as it may seem, and requires special attention! - if (xmpPacket_.size() == 0) { + if (xmpPacket.size() == 0) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Deleting XMP metadata is currently not supported.\n"; #endif @@ -451,7 +655,7 @@ namespace Exiv2 } // create temporary output file - BasicIo::AutoPtr tempIo(io_->temporary()); + BasicIo::AutoPtr tempIo(io.temporary()); assert (tempIo.get() != 0); if (!tempIo->isopen()) { #ifndef SUPPRESS_WARNINGS @@ -460,7 +664,7 @@ namespace Exiv2 throw Error(21); } #ifdef DEBUG - EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Created temporary file " << tempIo->path() << "\n"; + EXV_DEBUG << "readWriteEpsMetadata: Created temporary file " << tempIo->path() << "\n"; #endif // sort all positions @@ -504,7 +708,7 @@ namespace Exiv2 if (pos == size && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') { writeTemp(*tempIo, lineEnding); #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 } // update and complement DSC comments @@ -566,9 +770,9 @@ namespace Exiv2 // insert XMP metadata into existing flexible embedding if (pos == xmpPos) { 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; } } else { @@ -598,9 +802,9 @@ namespace Exiv2 if (photoshop) { writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop. " "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, "% &&end XMP packet marker&&" + lineEnding); writeTemp(*tempIo, "[/Document 1 dict begin" + lineEnding); @@ -635,273 +839,90 @@ namespace Exiv2 } // copy temporary file to real output file - io_->close(); - 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"; + io.close(); + io.transfer(*tempIo); } - #endif } - std::vector > EpsImage::findRemovableEmbeddings(const char* data, size_t posEof, size_t posEndPageSetup, - size_t xmpPos, size_t xmpSize, bool write) - { - std::vector > 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); - } - } +} // namespace - 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 << "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; - } +// ***************************************************************************** +// class member definitions +namespace Exiv2 +{ - 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 - std::vector > 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; + //LogMsg::setLevel(LogMsg::debug); + if (create) { + if (io_->open() == 0) { #ifdef DEBUG - EXV_DEBUG << "Exiv2::EpsImage::findXmp: Found XMP header at position: " << xmpPos << "\n"; + EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n"; #endif - - // prepare list of valid XMP trailers in the charset of the header - const std::string &charset = xmpHeaders[i].second; - std::vector > 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 << "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); - } + IoCloser closer(*io_); + if (io_->write(reinterpret_cast(epsBlank.data()), static_cast(epsBlank.size())) != static_cast(epsBlank.size())) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Failed to write blank EPS image.\n"; + #endif + throw Error(21); } - #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(); - 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; + return "application/postscript"; } - 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(); - 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; + throw Error(32, "Image comment", "EPS"); } - 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) - { - // 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; - } + // read metadata + readWriteEpsMetadata(*io_, xmpPacket_, /* write = */ false); - std::string EpsImage::toString(size_t size) - { - std::ostringstream stream; - stream << size; - return stream.str(); + // decode XMP metadata + if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_) > 1) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Failed to decode XMP metadata.\n"; + #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; - if (tempIo.write(reinterpret_cast(data), static_cast(size)) != static_cast(size)) { + #ifdef DEBUG + 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 - EXV_WARNING << "Failed to write to temporary file.\n"; + EXV_WARNING << "Failed to encode XMP metadata.\n"; #endif throw Error(21); } - } - void EpsImage::writeTemp(BasicIo& tempIo, const std::string &data) - { - writeTemp(tempIo, data.data(), data.size()); + // write metadata + readWriteEpsMetadata(*io_, xmpPacket_, /* write = */ true); + + #ifdef DEBUG + EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Finished writing EPS file " << io_->path() << "\n"; + #endif } // ************************************************************************* diff --git a/src/epsimage.hpp b/src/epsimage.hpp index e82366e5..b0673872 100644 --- a/src/epsimage.hpp +++ b/src/epsimage.hpp @@ -105,31 +105,6 @@ namespace Exiv2 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 > 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 // *****************************************************************************