diff --git a/include/exiv2/epsimage.hpp b/include/exiv2/epsimage.hpp new file mode 100644 index 00000000..54d26e7d --- /dev/null +++ b/include/exiv2/epsimage.hpp @@ -0,0 +1,124 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004-2021 Exiv2 authors + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/*! + @file epsimage.hpp + @brief EPS image. +
References: +
[1] Adobe PostScript Language Document Structuring Conventions Specification, Version 3.0, September 1992 +
[2] Adobe Encapsulated PostScript File Format Specification, Version 3.0, May 1992 +
[3] Adobe XMP Specification Part 3: Storage in Files, July 2010 +
[4] Re: Thumbnail data format in ai file, Dec 2003 + @author Michael Ulbrich (mul) + mul@rentapacs.de + @author Volker Grabsch (vog) + vog@notjusthosting.com + @date 7-Mar-2011, vog: created + */ +#ifndef EPSIMAGE_HPP_ +#define EPSIMAGE_HPP_ + +// ***************************************************************************** +#include "exiv2lib_export.h" + +// included header files +#include "image.hpp" + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 +{ + +// ***************************************************************************** +// class definitions + + // Add EPS to the supported image formats + namespace ImageType { + const int eps = 18; //!< EPS image type + } + + /*! + @brief Class to access EPS images. + */ + class EXIV2LIB_DEPRECATED_EXPORT EpsImage : public Image { + public: + //! @name Creators + //@{ + /*! + @brief Constructor to open a EPS image. Since the + constructor can't return a result, callers should check the + good() method after object construction to determine success + or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new file should be created (true). + */ + EpsImage(BasicIo::AutoPtr io, bool create); + //@} + + //! @name Manipulators + //@{ + void readMetadata(); + void writeMetadata(); + /*! + @brief Not supported. + Calling this function will throw an instance of Error(kerInvalidSettingForImage). + */ + void setComment(const std::string& comment); + //@} + + //! @name Accessors + //@{ + std::string mimeType() const; + //@} + + private: + //! @name NOT Implemented + //@{ + //! Copy constructor + EpsImage(const EpsImage& rhs); + //! Assignment operator + EpsImage& operator=(const EpsImage& rhs); + //@} + + }; // class EpsImage + +// ***************************************************************************** +// template, inline and free functions + + // These could be static private functions on Image subclasses but then + // ImageFactory needs to be made a friend. + /*! + @brief Create a new EpsImage instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ + EXIV2LIB_DEPRECATED_EXPORT Image::AutoPtr newEpsInstance(BasicIo::AutoPtr io, bool create); + + //! Check if the file iIo is a EPS image. + EXIV2LIB_DEPRECATED_EXPORT bool isEpsType(BasicIo& iIo, bool advance); + +} // namespace Exiv2 + +#endif // #ifndef EPSIMAGE_HPP_ diff --git a/include/exiv2/exiv2.hpp b/include/exiv2/exiv2.hpp index 5362d0b7..0e53235c 100644 --- a/include/exiv2/exiv2.hpp +++ b/include/exiv2/exiv2.hpp @@ -30,6 +30,7 @@ #include "exiv2/cr2image.hpp" #include "exiv2/crwimage.hpp" #include "exiv2/easyaccess.hpp" +#include "exiv2/epsimage.hpp" #include "exiv2/error.hpp" #include "exiv2/exif.hpp" #include "exiv2/futils.hpp" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73dc5e75..81053a31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,7 @@ add_library( exiv2lib crwimage.cpp ../include/exiv2/crwimage.hpp datasets.cpp ../include/exiv2/datasets.hpp easyaccess.cpp ../include/exiv2/easyaccess.hpp + epsimage.cpp ../include/exiv2/epsimage.hpp error.cpp ../include/exiv2/error.hpp exif.cpp ../include/exiv2/exif.hpp futils.cpp ../include/exiv2/futils.hpp diff --git a/src/epsimage.cpp b/src/epsimage.cpp new file mode 100644 index 00000000..79402e62 --- /dev/null +++ b/src/epsimage.cpp @@ -0,0 +1,1188 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004-2021 Exiv2 authors + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/* + File: epsimage.cpp + Author(s): Michael Ulbrich (mul) + Volker Grabsch (vog) + History: 7-Mar-2011, vog: created + */ +// ***************************************************************************** +// included header files +#include "config.h" + +#include "epsimage.hpp" +#include "image.hpp" +#include "basicio.hpp" +#include "error.hpp" +#include "futils.hpp" +#include "version.hpp" + +// + standard includes +#include +#include +#include +#include +#include +#include +#include + +// ***************************************************************************** +namespace { + + using namespace Exiv2; + using Exiv2::byte; + + // signature of DOS EPS + const std::string dosEpsSignature = "\xC5\xD0\xD3\xC6"; + + // first line of EPS + 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 + const std::string epsBlank = "%!PS-Adobe-3.0 EPSF-3.0\n" + "%%BoundingBox: 0 0 0 0\n"; + + // list of all valid XMP headers + const std::string xmpHeaders[] = { + + // 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 + void writeTemp(BasicIo& tempIo, const byte* data, size_t size) + { + if (size == 0) return; + if (tempIo.write(data, static_cast(size)) != static_cast(size)) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Failed to write to temporary file.\n"; + #endif + throw Error(kerImageWriteFailed); + } + } + + //! Write data into temp file, taking care of errors + void writeTemp(BasicIo& tempIo, const std::string &data) + { + writeTemp(tempIo, reinterpret_cast(data.data()), data.size()); + } + + //! Get the current write position of temp file, taking care of errors + uint32_t posTemp(BasicIo& tempIo) + { + const long pos = tempIo.tell(); + if (pos == -1) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Internal error while determining current write position in temporary file.\n"; + #endif + throw Error(kerImageWriteFailed); + } + return static_cast(pos); + } + + //! Check whether a string has a certain beginning + bool startsWith(const std::string& s, const std::string& start) + { + return s.size() >= start.size() && memcmp(s.data(), start.data(), start.size()) == 0; + } + + //! Check whether a string contains only white space characters + bool 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 the next line of a buffer, allow for changing line ending style + size_t readLine(std::string& line, const byte* 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 + size_t readPrevLine(std::string& line, const byte* 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 + void findXmp(size_t& xmpPos, size_t& xmpSize, const byte* data, size_t startPos, size_t size, bool write) + { + // search for valid XMP header + xmpSize = 0; + for (xmpPos = startPos; xmpPos < size; xmpPos++) { + if (data[xmpPos] != '\x00' && data[xmpPos] != '<') continue; + for (size_t i = 0; i < (sizeof xmpHeaders) / (sizeof *xmpHeaders); i++) { + const std::string &header = xmpHeaders[i]; + 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 + + // 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 < (sizeof xmpTrailers) / (sizeof *xmpTrailers); j++) { + const std::string &trailer = xmpTrailers[j].trailer; + const bool readOnly = xmpTrailers[j].readOnly; + + 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 + + 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 ? kerImageWriteFailed : kerFailedToReadImageData); + } + + // 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 ? kerImageWriteFailed : kerFailedToReadImageData); + } + } + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Found XMP header but no XMP trailer.\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + } + } + + //! Unified implementation of reading and writing EPS metadata + void readWriteEpsMetadata(BasicIo& io, std::string& xmpPacket, NativePreviewList& nativePreviews, bool write) + { + // open input file + if (io.open() != 0) { + throw Error(kerDataSourceOpenFailed, io.path(), strError()); + } + IoCloser closer(io); + + // read from input file via memory map + const byte *data = io.mmap(); + + // default positions and sizes + const size_t size = io.size(); + size_t posEps = 0; + size_t posEndEps = size; + uint32_t posWmf = 0; + uint32_t sizeWmf = 0; + uint32_t posTiff = 0; + uint32_t sizeTiff = 0; + + // check for DOS EPS + const bool dosEps = (size >= dosEpsSignature.size() && memcmp(data, dosEpsSignature.data(), dosEpsSignature.size()) == 0); + if (dosEps) { + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found DOS EPS signature\n"; + #endif + if (size < 30) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Premature end of file after DOS EPS signature.\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + posEps = getULong(data + 4, littleEndian); + posEndEps = getULong(data + 8, littleEndian) + posEps; + posWmf = getULong(data + 12, littleEndian); + sizeWmf = getULong(data + 16, littleEndian); + posTiff = getULong(data + 20, littleEndian); + sizeTiff = getULong(data + 24, littleEndian); + const uint16_t checksum = getUShort(data + 28, littleEndian); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: EPS section at position " << posEps << ", size " << (posEndEps - posEps) << "\n"; + EXV_DEBUG << "readWriteEpsMetadata: WMF section at position " << posWmf << ", size " << sizeWmf << "\n"; + EXV_DEBUG << "readWriteEpsMetadata: TIFF section at position " << posTiff << ", size " << sizeTiff << "\n"; + #endif + if (checksum != 0xFFFF) { + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: DOS EPS checksum is not FFFF\n"; + #endif + } + if (!((posWmf == 0 && sizeWmf == 0) || (posTiff == 0 && sizeTiff == 0))) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "DOS EPS file has both WMF and TIFF section. Only one of those is allowed.\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + } + if (sizeWmf == 0 && sizeTiff == 0) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "DOS EPS file has neither WMF nor TIFF section. Exactly one of those is required.\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + } + if (posEps < 30 || posEndEps > size) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "DOS EPS file has invalid position (" << posEps << ") or size (" << (posEndEps - posEps) << ") for EPS section.\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + if (sizeWmf != 0 && (posWmf < 30 || posWmf + sizeWmf > size)) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "DOS EPS file has invalid position (" << posWmf << ") or size (" << sizeWmf << ") for WMF section.\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + } + if (sizeTiff != 0 && (posTiff < 30 || posTiff + sizeTiff > size)) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "DOS EPS file has invalid position (" << posTiff << ") or size (" << sizeTiff << ") for TIFF section.\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + } + } + + // check first line + std::string firstLine; + const size_t posSecondLine = readLine(firstLine, data, posEps, posEndEps); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: First line: " << firstLine << "\n"; + #endif + bool matched = false; + for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { + matched = (firstLine == epsFirstLine[i]); + } + if (!matched) { + throw Error(kerNotAnImage, "EPS"); + } + + // determine line ending style of the first line + if (posSecondLine >= posEndEps) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Premature end of file after first line.\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + const std::string lineEnding(reinterpret_cast(data + posEps + firstLine.size()), posSecondLine - (posEps + firstLine.size())); + #ifdef DEBUG + if (lineEnding == "\n") { + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Unix (LF)\n"; + } else if (lineEnding == "\r") { + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Mac (CR)\n"; + } else if (lineEnding == "\r\n") { + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: DOS (CR LF)\n"; + } else { + EXV_DEBUG << "readWriteEpsMetadata: Line ending style: (unknown)\n"; + } + #endif + + // scan comments + size_t posLanguageLevel = posEndEps; + size_t posContainsXmp = posEndEps; + size_t posPages = posEndEps; + size_t posExiv2Version = posEndEps; + size_t posExiv2Website = posEndEps; + size_t posEndComments = posEndEps; + size_t posAi7Thumbnail = posEndEps; + size_t posAi7ThumbnailEndData = posEndEps; + size_t posBeginPhotoshop = posEndEps; + size_t posEndPhotoshop = posEndEps; + size_t posPage = posEndEps; + size_t posBeginPageSetup = posEndEps; + size_t posEndPageSetup = posEndEps; + size_t posPageTrailer = posEndEps; + size_t posEof = posEndEps; + std::vector > removableEmbeddings; + unsigned int depth = 0; + const unsigned int maxDepth = UINT_MAX; + bool illustrator8 = false; + bool corelDraw = false; + bool implicitPage = false; + bool implicitPageSetup = false; + bool implicitPageTrailer = false; + bool inDefaultsPreviewPrologSetup = false; + bool inRemovableEmbedding = false; + std::string removableEmbeddingEndLine; + unsigned int removableEmbeddingsWithUnmarkedTrailer = 0; + for (size_t pos = posEps; pos < posEof;) { + const size_t startPos = pos; + std::string line; + pos = readLine(line, data, startPos, posEndEps); + #ifdef DEBUG + bool significantLine = true; + #endif + // nested documents + if (posPage == posEndEps && (startsWith(line, "%%IncludeDocument:") || startsWith(line, "%%BeginDocument:"))) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Nested document at invalid position: " << startPos << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } else if (startsWith(line, "%%BeginDocument:")) { + if (depth == maxDepth) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Document too deeply nested at position: " << startPos << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + depth++; + } else if (startsWith(line, "%%EndDocument")) { + if (depth == 0) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unmatched EndDocument at position: " << startPos << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + depth--; + } else { + #ifdef DEBUG + significantLine = false; + #endif + } + #ifdef DEBUG + if (significantLine) { + EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; + } + significantLine = true; + #endif + if (depth != 0) continue; + // explicit "Begin" comments + if (startsWith(line, "%%BeginPreview:")) { + inDefaultsPreviewPrologSetup = true; + } else if (line == "%%BeginDefaults") { + inDefaultsPreviewPrologSetup = true; + } else if (line == "%%BeginProlog") { + inDefaultsPreviewPrologSetup = true; + } else if (line == "%%BeginSetup") { + inDefaultsPreviewPrologSetup = true; + } else if (posPage == posEndEps && startsWith(line, "%%Page:")) { + posPage = startPos; + } else if (posPage != posEndEps && startsWith(line, "%%Page:")) { + if (implicitPage) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Page at position " << startPos << " conflicts with implicit page at position: " << posPage << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to handle multiple PostScript pages. Found second page at position: " << startPos << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } else if (line == "%%BeginPageSetup") { + posBeginPageSetup = startPos; + } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: Before %%EndPageSetup") { + inRemovableEmbedding = true; + removableEmbeddings.push_back(std::make_pair(startPos, startPos)); + removableEmbeddingEndLine = "%Exiv2EndXMP"; + } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: After %%PageTrailer") { + inRemovableEmbedding = true; + removableEmbeddings.push_back(std::make_pair(startPos, startPos)); + removableEmbeddingEndLine = "%Exiv2EndXMP"; + } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageSetup End \"AI11EPS\"") { + inRemovableEmbedding = true; + removableEmbeddings.push_back(std::make_pair(startPos, startPos)); + removableEmbeddingEndLine = "%ADOEndClientInjection: PageSetup End \"AI11EPS\""; + } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageTrailer Start \"AI11EPS\"") { + inRemovableEmbedding = true; + removableEmbeddings.push_back(std::make_pair(startPos, startPos)); + removableEmbeddingEndLine = "%ADOEndClientInjection: PageTrailer Start \"AI11EPS\""; + } else if (!inRemovableEmbedding && line == "%begin_xml_code") { + inRemovableEmbedding = true; + removableEmbeddings.push_back(std::make_pair(startPos, startPos)); + removableEmbeddingEndLine = "%end_xml_code"; + removableEmbeddingsWithUnmarkedTrailer++; + } else { + #ifdef DEBUG + significantLine = false; + #endif + } + #ifdef DEBUG + if (significantLine) { + EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; + } + significantLine = true; + #endif + // implicit comments + if (line == "%%EOF" || line == "%begin_xml_code" || !(line.size() >= 2 && line[0] == '%' && '\x21' <= line[1] && line[1] <= '\x7e')) { + if (posEndComments == posEndEps) { + posEndComments = startPos; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndComments at position: " << startPos << "\n"; + #endif + } + } + if (posPage == posEndEps && posEndComments != posEndEps && !inDefaultsPreviewPrologSetup && !inRemovableEmbedding && !onlyWhitespaces(line)) { + posPage = startPos; + implicitPage = true; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit Page at position: " << startPos << "\n"; + #endif + } + if (posBeginPageSetup == posEndEps && (implicitPage || (posPage != posEndEps && !inRemovableEmbedding && line.size() >= 1 && line[0] != '%'))) { + posBeginPageSetup = startPos; + implicitPageSetup = true; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n"; + #endif + } + if (posEndPageSetup == posEndEps && implicitPageSetup && !inRemovableEmbedding && line.size() >= 1 && line[0] != '%') { + posEndPageSetup = startPos; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n"; + #endif + } + if (line.size() >= 1 && line[0] != '%') continue; // performance optimization + if (line == "%%EOF" || line == "%%Trailer" || line == "%%PageTrailer") { + if (posBeginPageSetup == posEndEps) { + posBeginPageSetup = startPos; + implicitPageSetup = true; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n"; + #endif + } + if (posEndPageSetup == posEndEps) { + posEndPageSetup = startPos; + implicitPageSetup = true; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n"; + #endif + } + } + if (line == "%%EOF" || line == "%%Trailer") { + if (posPageTrailer == posEndEps) { + posPageTrailer = startPos; + implicitPageTrailer = true; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Found implicit PageTrailer at position: " << startPos << "\n"; + #endif + } + } + // remaining explicit comments + if (posEndComments == posEndEps && posLanguageLevel == posEndEps && startsWith(line, "%%LanguageLevel:")) { + posLanguageLevel = startPos; + } else if (posEndComments == posEndEps && posContainsXmp == posEndEps && startsWith(line, "%ADO_ContainsXMP:")) { + posContainsXmp = startPos; + } else if (posEndComments == posEndEps && posPages == posEndEps && startsWith(line, "%%Pages:")) { + posPages = startPos; + } else if (posEndComments == posEndEps && posExiv2Version == posEndEps && startsWith(line, "%Exiv2Version:")) { + posExiv2Version = startPos; + } else if (posEndComments == posEndEps && posExiv2Website == posEndEps && startsWith(line, "%Exiv2Website:")) { + posExiv2Website = startPos; + } else if (posEndComments == posEndEps && startsWith(line, "%%Creator: Adobe Illustrator") && firstLine == "%!PS-Adobe-3.0 EPSF-3.0") { + illustrator8 = true; + } else if (posEndComments == posEndEps && startsWith(line, "%AI7_Thumbnail:")) { + posAi7Thumbnail = startPos; + } else if (posEndComments == posEndEps && posAi7Thumbnail != posEndEps && posAi7ThumbnailEndData == posEndEps && line == "%%EndData") { + posAi7ThumbnailEndData = startPos; + } else if (posEndComments == posEndEps && line == "%%EndComments") { + posEndComments = startPos; + } else if (inDefaultsPreviewPrologSetup && startsWith(line, "%%BeginResource: procset wCorel")) { + corelDraw = true; + } else if (line == "%%EndPreview") { + inDefaultsPreviewPrologSetup = false; + } else if (line == "%%EndDefaults") { + inDefaultsPreviewPrologSetup = false; + } else if (line == "%%EndProlog") { + inDefaultsPreviewPrologSetup = false; + } else if (line == "%%EndSetup") { + inDefaultsPreviewPrologSetup = false; + } else if (posEndPageSetup == posEndEps && line == "%%EndPageSetup") { + posEndPageSetup = startPos; + } else if (posPageTrailer == posEndEps && line == "%%PageTrailer") { + posPageTrailer = startPos; + } else if (posBeginPhotoshop == posEndEps && startsWith(line, "%BeginPhotoshop:")) { + posBeginPhotoshop = pos; + } else if (posBeginPhotoshop != posEndEps && posEndPhotoshop == posEndEps && line == "%EndPhotoshop") { + posEndPhotoshop = startPos; + } else if (inRemovableEmbedding && line == removableEmbeddingEndLine) { + inRemovableEmbedding = false; + removableEmbeddings.back().second = pos; + } else if (line == "%%EOF") { + posEof = startPos; + } else { + #ifdef DEBUG + significantLine = false; + #endif + } + #ifdef DEBUG + if (significantLine) { + EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; + } + #endif + } + + // check for unfinished nested documents + if (depth != 0) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unmatched BeginDocument (" << depth << "x)\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + + // look for the unmarked trailers of some removable XMP embeddings + size_t posXmpTrailerEnd = posEof; + for (size_t i = 0; i < removableEmbeddingsWithUnmarkedTrailer; i++) { + std::string line1; + const size_t posLine1 = readPrevLine(line1, data, posXmpTrailerEnd, posEndEps); + std::string line2; + const size_t posLine2 = readPrevLine(line2, data, posLine1, posEndEps); + size_t posXmpTrailer; + if (line1 == "[/EMC pdfmark") { // Exiftool style + posXmpTrailer = posLine1; + } else if (line1 == "[/NamespacePop pdfmark" && + line2 == "[{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT pdfmark") { // Photoshop style + posXmpTrailer = posLine2; + } else { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to find XMP embedding trailer ending at position: " << posXmpTrailerEnd << "\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + break; + } + removableEmbeddings.push_back(std::make_pair(posXmpTrailer, posXmpTrailerEnd)); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Recognized unmarked trailer of removable XMP embedding at " + "[" << removableEmbeddings.back().first << "," << removableEmbeddings.back().second << ")" + "\n"; + #endif + posXmpTrailerEnd = posXmpTrailer; + } + + // interpret comment "%ADO_ContainsXMP:" + std::string line; + readLine(line, data, posContainsXmp, posEndEps); + bool containsXmp; + if (line == "%ADO_ContainsXMP: MainFirst" || line == "%ADO_ContainsXMP:MainFirst") { + containsXmp = true; + } else if (line == "" || line == "%ADO_ContainsXMP: NoMain" || line == "%ADO_ContainsXMP:NoMain") { + containsXmp = false; + } else { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Invalid line \"" << line << "\" at position: " << posContainsXmp << "\n"; + #endif + throw Error(write ? kerImageWriteFailed : kerFailedToReadImageData); + } + + const bool deleteXmp = (write && xmpPacket.size() == 0); + bool fixBeginXmlPacket = false; + bool useFlexibleEmbedding = false; + size_t xmpPos = posEndEps; + size_t xmpSize = 0; + if (containsXmp) { + // search for XMP metadata + findXmp(xmpPos, xmpSize, data, posEps, posEndEps, write); + if (xmpPos == posEndEps) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to find XMP metadata as announced at position: " << posContainsXmp << "\n"; + #endif + } + // check embedding of XMP metadata + const size_t posLineAfterXmp = readLine(line, data, xmpPos + xmpSize, posEndEps); + if (line != "") { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unexpected " << line.size() << " bytes of data after XMP at position: " << (xmpPos + xmpSize) << "\n"; + #endif + } else if (!deleteXmp) { + readLine(line, data, posLineAfterXmp, posEndEps); + if (line == "% &&end XMP packet marker&&" || line == "% &&end XMP packet marker&&") { + useFlexibleEmbedding = true; + } + } + } + if (useFlexibleEmbedding) { + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Using flexible XMP embedding\n"; + #endif + const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, posEndEps); + if (startsWith(line, "%begin_xml_packet:")) { + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: XMP embedding contains %begin_xml_packet\n"; + #endif + if (write) { + fixBeginXmlPacket = true; + xmpSize += (xmpPos - posBeginXmlPacket); + xmpPos = posBeginXmlPacket; + } + } else if (posBeginPhotoshop != posEndEps) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Missing %begin_xml_packet in Photoshop EPS at position: " << xmpPos << "\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + } + } + if (!useFlexibleEmbedding) { + // check if there are irremovable XMP metadata blocks before EndPageSetup + size_t posOtherXmp = containsXmp ? xmpPos : posEps; + size_t sizeOtherXmp = 0; + for (;;) { + findXmp(posOtherXmp, sizeOtherXmp, data, posOtherXmp + sizeOtherXmp, posEndPageSetup, write); + if (posOtherXmp >= posEndPageSetup) break; + bool isRemovableEmbedding = false; + for (std::vector >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); ++e) { + if (e->first <= posOtherXmp && posOtherXmp < e->second) { + isRemovableEmbedding = true; + break; + } + } + if (!isRemovableEmbedding) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "XMP metadata block is not removable at position: " << posOtherXmp << "\n"; + #endif + if (write) throw Error(kerImageWriteFailed); + break; + } + } + } + + if (!write) { + // copy XMP metadata + xmpPacket.assign(reinterpret_cast(data + xmpPos), xmpSize); + + // native previews + nativePreviews.clear(); + if (posAi7ThumbnailEndData != posEndEps) { + NativePreview nativePreview; + std::string dummy; + std::string lineAi7Thumbnail; + const size_t posBeginData = readLine(lineAi7Thumbnail, data, posAi7Thumbnail, posEndEps); + std::istringstream lineStreamAi7Thumbnail(lineAi7Thumbnail); + lineStreamAi7Thumbnail >> dummy; + lineStreamAi7Thumbnail >> nativePreview.width_; + lineStreamAi7Thumbnail >> nativePreview.height_; + std::string depth; + lineStreamAi7Thumbnail >> depth; + std::string lineBeginData; + const size_t posAfterBeginData = readLine(lineBeginData, data, posBeginData, posEndEps); + std::istringstream lineStreamBeginData(lineBeginData); + std::string beginData; + lineStreamBeginData >> beginData; + lineStreamBeginData >> dummy; + std::string type; + lineStreamBeginData >> type; + nativePreview.position_ = static_cast(posAfterBeginData); + nativePreview.size_ = static_cast(posAi7ThumbnailEndData - posAfterBeginData); + nativePreview.filter_ = "hex-ai7thumbnail-pnm"; + nativePreview.mimeType_ = "image/x-portable-anymap"; + if (depth != "8") { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to handle Illustrator thumbnail depth: " << depth << "\n"; + #endif + } else if (beginData != "%%BeginData:") { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to handle Illustrator thumbnail data section: " << lineBeginData << "\n"; + #endif + } else if (type != "Hex") { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to handle Illustrator thumbnail data type: " << type << "\n"; + #endif + } else { + nativePreviews.push_back(nativePreview); + } + } + if (posEndPhotoshop != posEndEps) { + NativePreview nativePreview; + nativePreview.position_ = static_cast(posBeginPhotoshop); + nativePreview.size_ = static_cast(posEndPhotoshop - posBeginPhotoshop); + nativePreview.width_ = 0; + nativePreview.height_ = 0; + nativePreview.filter_ = "hex-irb"; + nativePreview.mimeType_ = "image/jpeg"; + nativePreviews.push_back(nativePreview); + } + if (sizeWmf != 0) { + NativePreview nativePreview; + nativePreview.position_ = static_cast(posWmf); + nativePreview.size_ = sizeWmf; + nativePreview.width_ = 0; + nativePreview.height_ = 0; + nativePreview.filter_ = ""; + nativePreview.mimeType_ = "image/x-wmf"; + nativePreviews.push_back(nativePreview); + } + if (sizeTiff != 0) { + NativePreview nativePreview; + nativePreview.position_ = static_cast(posTiff); + nativePreview.size_ = sizeTiff; + nativePreview.width_ = 0; + nativePreview.height_ = 0; + nativePreview.filter_ = ""; + nativePreview.mimeType_ = "image/tiff"; + nativePreviews.push_back(nativePreview); + } + } else { + // check for Adobe Illustrator 8.0 or older + if (illustrator8) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to write to EPS files created by Adobe Illustrator 8.0 or older.\n"; + #endif + throw Error(kerImageWriteFailed); + } + + // create temporary output file + BasicIo::AutoPtr tempIo(new MemIo); + assert (tempIo.get() != 0); + if (!tempIo->isopen()) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Unable to create temporary file for writing.\n"; + #endif + throw Error(kerImageWriteFailed); + } + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Created temporary file " << tempIo->path() << "\n"; + #endif + + // sort all positions + std::vector positions; + positions.push_back(posLanguageLevel); + positions.push_back(posContainsXmp); + positions.push_back(posPages); + positions.push_back(posExiv2Version); + positions.push_back(posExiv2Website); + positions.push_back(posEndComments); + positions.push_back(posPage); + positions.push_back(posBeginPageSetup); + positions.push_back(posEndPageSetup); + positions.push_back(posPageTrailer); + positions.push_back(posEof); + positions.push_back(posEndEps); + if (useFlexibleEmbedding) { + positions.push_back(xmpPos); + } + for (std::vector >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); ++e) { + positions.push_back(e->first); + } + std::sort(positions.begin(), positions.end()); + + // assemble result EPS document + if (dosEps) { + // DOS EPS header will be written afterwards + writeTemp(*tempIo, std::string(30, '\x00')); + } + const std::string containsXmpLine = deleteXmp ? "%ADO_ContainsXMP: NoMain" : "%ADO_ContainsXMP: MainFirst"; + const uint32_t posEpsNew = posTemp(*tempIo); + size_t prevPos = posEps; + size_t prevSkipPos = prevPos; + for (std::vector::const_iterator i = positions.begin(); i != positions.end(); ++i) { + const size_t pos = *i; + if (pos == prevPos) continue; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Writing at " << pos << "\n"; + #endif + if (pos < prevSkipPos) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Internal error while assembling the result EPS document: " + "Unable to continue at position " << pos << " after skipping to position " << prevSkipPos << "\n"; + #endif + throw Error(kerImageWriteFailed); + } + writeTemp(*tempIo, data + prevSkipPos, pos - prevSkipPos); + const size_t posLineEnd = readLine(line, data, pos, posEndEps); + size_t skipPos = pos; + // add last line ending if necessary + if (pos == posEndEps && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') { + writeTemp(*tempIo, lineEnding); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Added missing line ending of last line\n"; + #endif + } + // update and complement DSC comments + if (pos == posLanguageLevel && posLanguageLevel != posEndEps && !deleteXmp && !useFlexibleEmbedding) { + if (line == "%%LanguageLevel:1" || line == "%%LanguageLevel: 1") { + writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding); + skipPos = posLineEnd; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + } + if (pos == posContainsXmp && posContainsXmp != posEndEps) { + if (line != containsXmpLine) { + writeTemp(*tempIo, containsXmpLine + lineEnding); + skipPos = posLineEnd; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + } + if (pos == posExiv2Version && posExiv2Version != posEndEps) { + writeTemp(*tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding); + skipPos = posLineEnd; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + if (pos == posExiv2Website && posExiv2Website != posEndEps) { + writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); + skipPos = posLineEnd; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + if (pos == posEndComments) { + if (posLanguageLevel == posEndEps && !deleteXmp && !useFlexibleEmbedding) { + writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding); + } + if (posContainsXmp == posEndEps) { + writeTemp(*tempIo, containsXmpLine + lineEnding); + } + if (posPages == posEndEps) { + writeTemp(*tempIo, "%%Pages: 1" + lineEnding); + } + if (posExiv2Version == posEndEps) { + writeTemp(*tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding); + } + if (posExiv2Website == posEndEps) { + writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); + } + readLine(line, data, posEndComments, posEndEps); + if (line != "%%EndComments") { + writeTemp(*tempIo, "%%EndComments" + lineEnding); + } + } + if (pos == posPage) { + if (!startsWith(line, "%%Page:")) { + writeTemp(*tempIo, "%%Page: 1 1" + lineEnding); + writeTemp(*tempIo, "%%EndPageComments" + lineEnding); + } + } + if (pos == posBeginPageSetup) { + if (line != "%%BeginPageSetup") { + writeTemp(*tempIo, "%%BeginPageSetup" + lineEnding); + } + } + if (useFlexibleEmbedding) { + // insert XMP metadata into existing flexible embedding + if (pos == xmpPos) { + if (fixBeginXmlPacket) { + writeTemp(*tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding); + } + writeTemp(*tempIo, xmpPacket); + skipPos += xmpSize; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + } + if (!useFlexibleEmbedding) { + // remove preceding embedding(s) + for (std::vector >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); ++e) { + if (pos == e->first) { + skipPos = e->second; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + break; + } + } + // insert XMP metadata with new flexible embedding, if necessary + if (pos == posEndPageSetup && !deleteXmp) { + writeTemp(*tempIo, "%Exiv2BeginXMP: Before %%EndPageSetup" + lineEnding); + if (corelDraw) { + writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding); + writeTemp(*tempIo, "@rs" + lineEnding); + } + if (posBeginPhotoshop != posEndEps) { + writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding); + writeTemp(*tempIo, "%begin_xml_code" + lineEnding); + } + writeTemp(*tempIo, "/currentdistillerparams where" + lineEnding); + writeTemp(*tempIo, "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse" + lineEnding); + writeTemp(*tempIo, "{userdict /Exiv2_pdfmark /cleartomark load put" + lineEnding); + writeTemp(*tempIo, " userdict /Exiv2_metafile_pdfmark {flushfile cleartomark} bind put}" + lineEnding); + writeTemp(*tempIo, "{userdict /Exiv2_pdfmark /pdfmark load put" + lineEnding); + writeTemp(*tempIo, " userdict /Exiv2_metafile_pdfmark {/PUT pdfmark} bind put} ifelse" + lineEnding); + writeTemp(*tempIo, "[/NamespacePush Exiv2_pdfmark" + lineEnding); + writeTemp(*tempIo, "[/_objdef {Exiv2_metadata_stream} /type /stream /OBJ Exiv2_pdfmark" + lineEnding); + writeTemp(*tempIo, "[{Exiv2_metadata_stream} 2 dict begin" + lineEnding); + writeTemp(*tempIo, " /Type /Metadata def /Subtype /XML def currentdict end /PUT Exiv2_pdfmark" + lineEnding); + writeTemp(*tempIo, "[{Exiv2_metadata_stream}" + lineEnding); + writeTemp(*tempIo, " currentfile 0 (% &&end XMP packet marker&&)" + lineEnding); + writeTemp(*tempIo, " /SubFileDecode filter Exiv2_metafile_pdfmark" + lineEnding); + if (posBeginPhotoshop != posEndEps) { + 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, xmpPacket); + writeTemp(*tempIo, lineEnding); + writeTemp(*tempIo, "% &&end XMP packet marker&&" + lineEnding); + writeTemp(*tempIo, "[/Document 1 dict begin" + lineEnding); + writeTemp(*tempIo, " /Metadata {Exiv2_metadata_stream} def currentdict end /BDC Exiv2_pdfmark" + lineEnding); + if (posBeginPhotoshop != posEndEps) { + writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding); + writeTemp(*tempIo, "%end_xml_code" + lineEnding); + } + if (corelDraw) { + writeTemp(*tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding); + writeTemp(*tempIo, "@sv" + lineEnding); + } + writeTemp(*tempIo, "%Exiv2EndXMP" + lineEnding); + } + } + if (pos == posEndPageSetup) { + if (line != "%%EndPageSetup") { + writeTemp(*tempIo, "%%EndPageSetup" + lineEnding); + } + } + if (!useFlexibleEmbedding) { + if (pos == posPageTrailer && !deleteXmp) { + if (!implicitPageTrailer) { + skipPos = posLineEnd; + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; + #endif + } + writeTemp(*tempIo, "%%PageTrailer" + lineEnding); + writeTemp(*tempIo, "%Exiv2BeginXMP: After %%PageTrailer" + lineEnding); + writeTemp(*tempIo, "[/EMC Exiv2_pdfmark" + lineEnding); + writeTemp(*tempIo, "[/NamespacePop Exiv2_pdfmark" + lineEnding); + writeTemp(*tempIo, "%Exiv2EndXMP" + lineEnding); + } + } + // add EOF comment if necessary + if (pos == posEndEps && posEof == posEndEps) { + writeTemp(*tempIo, "%%EOF" + lineEnding); + } + prevPos = pos; + prevSkipPos = skipPos; + } + const uint32_t posEndEpsNew = posTemp(*tempIo); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: New EPS size: " << (posEndEpsNew - posEpsNew) << "\n"; + #endif + if (dosEps) { + // write WMF and/or TIFF section if present + writeTemp(*tempIo, data + posWmf, sizeWmf); + writeTemp(*tempIo, data + posTiff, sizeTiff); + #ifdef DEBUG + EXV_DEBUG << "readWriteEpsMetadata: New DOS EPS total size: " << posTemp(*tempIo) << "\n"; + #endif + // write DOS EPS header + if (tempIo->seek(0, BasicIo::beg) != 0) { + #ifndef SUPPRESS_WARNINGS + EXV_WARNING << "Internal error while seeking in temporary file.\n"; + #endif + throw Error(kerImageWriteFailed); + } + byte dosEpsHeader[30]; + dosEpsSignature.copy(reinterpret_cast(dosEpsHeader), dosEpsSignature.size()); + ul2Data(dosEpsHeader + 4, posEpsNew, littleEndian); + ul2Data(dosEpsHeader + 8, posEndEpsNew - posEpsNew, littleEndian); + ul2Data(dosEpsHeader + 12, sizeWmf == 0 ? 0 : posEndEpsNew, littleEndian); + ul2Data(dosEpsHeader + 16, sizeWmf, littleEndian); + ul2Data(dosEpsHeader + 20, sizeTiff == 0 ? 0 : posEndEpsNew + sizeWmf, littleEndian); + ul2Data(dosEpsHeader + 24, sizeTiff, littleEndian); + us2Data(dosEpsHeader + 28, 0xFFFF, littleEndian); + writeTemp(*tempIo, dosEpsHeader, sizeof(dosEpsHeader)); + } + + // copy temporary file to real output file + io.close(); + io.transfer(*tempIo); + } + } + +} // namespace + +// ***************************************************************************** +// class member definitions +namespace Exiv2 +{ + + EpsImage::EpsImage(BasicIo::AutoPtr io, bool create) + : Image(ImageType::eps, mdXmp, io) + { + //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(kerImageWriteFailed); + } + } + } + } + + std::string EpsImage::mimeType() const + { + return "application/postscript"; + } + + void EpsImage::setComment(const std::string& /*comment*/) + { + throw Error(kerInvalidSettingForImage, "Image comment", "EPS"); + } + + void EpsImage::readMetadata() + { + #ifdef DEBUG + EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Reading EPS file " << io_->path() << "\n"; + #endif + + // read metadata + readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ false); + + // 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(kerFailedToReadImageData); + } + + #ifdef DEBUG + EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Finished reading EPS file " << io_->path() << "\n"; + #endif + } + + void EpsImage::writeMetadata() + { + #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 encode XMP metadata.\n"; + #endif + throw Error(kerImageWriteFailed); + } + + // write metadata + readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ true); + + #ifdef DEBUG + EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Finished writing EPS file " << io_->path() << "\n"; + #endif + } + + // ************************************************************************* + // free functions + Image::AutoPtr newEpsInstance(BasicIo::AutoPtr io, bool create) + { + Image::AutoPtr image(new EpsImage(io, create)); + if (!image->good()) { + image.reset(); + } + return image; + } + + bool isEpsType(BasicIo& iIo, bool advance) + { + // read as many bytes as needed for the longest (DOS) EPS signature + long bufSize = static_cast(dosEpsSignature.size()); + for (size_t i = 0; i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { + if (bufSize < static_cast(epsFirstLine[i].size())) { + bufSize = static_cast(epsFirstLine[i].size()); + } + } + DataBuf buf = iIo.read(bufSize); + if (iIo.error() || buf.size_ != bufSize) { + return false; + } + // check for all possible (DOS) EPS signatures + bool matched = (memcmp(buf.pData_, dosEpsSignature.data(), dosEpsSignature.size()) == 0); + for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { + matched = (memcmp(buf.pData_, epsFirstLine[i].data(), epsFirstLine[i].size()) == 0); + } + // seek back if possible and requested + if (!advance || !matched) { + iIo.seek(-buf.size_, BasicIo::cur); + } + return matched; + } + +} // namespace Exiv2 diff --git a/src/image.cpp b/src/image.cpp index 3c0dc3e1..afb49816 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -33,6 +33,7 @@ #endif// EXV_ENABLE_BMFF #include "cr2image.hpp" #include "crwimage.hpp" +#include "epsimage.hpp" #include "jpgimage.hpp" #include "mrwimage.hpp" #ifdef EXV_HAVE_LIBZ @@ -117,6 +118,7 @@ namespace { #endif // EXV_HAVE_LIBZ { ImageType::pgf, newPgfInstance, isPgfType, amReadWrite, amReadWrite, amReadWrite, amReadWrite }, { ImageType::raf, newRafInstance, isRafType, amRead, amRead, amRead, amNone }, + { ImageType::eps, newEpsInstance, isEpsType, amNone, amNone, amReadWrite, amNone }, { ImageType::xmp, newXmpInstance, isXmpType, amReadWrite, amReadWrite, amReadWrite, amNone }, { ImageType::gif, newGifInstance, isGifType, amNone, amNone, amNone, amNone }, { ImageType::psd, newPsdInstance, isPsdType, amReadWrite, amReadWrite, amReadWrite, amNone },