// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2004-2011 Andreas Huggel * * 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 Version: $Rev: 2455 $ Author(s): Michael Ulbrich (mul) Volker Grabsch (vog) History: 7-Mar-2011, vog: created */ // ***************************************************************************** #include "rcsid_int.hpp" EXIV2_RCSID("@(#) $Id: epsimage.cpp $") // ***************************************************************************** //#define DEBUG 1 // ***************************************************************************** // included header files #ifdef _MSC_VER # include "exv_msvc.h" #else # include "exv_conf.h" #endif #include "epsimage.hpp" #include "image.hpp" #include "basicio.hpp" #include "convert.hpp" #include "error.hpp" #include "futils.hpp" // + standard includes #include #include #include #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) { //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); } } } } std::string EpsImage::mimeType() const { return "application/postscript"; } void EpsImage::setComment(const std::string& /*comment*/) { throw Error(32, "Image comment", "EPS"); } void EpsImage::readMetadata() { doReadWriteMetadata(/* write = */ false); } void EpsImage::writeMetadata() { doReadWriteMetadata(/* write = */ true); } void EpsImage::doReadWriteMetadata(bool write) { #ifdef DEBUG if (write) { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Writing EPS file " << io_->path() << "\n"; } else { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Reading EPS file " << io_->path() << "\n"; } #endif // open input file if (io_->open() != 0) { throw Error(9, io_->path(), strError()); } IoCloser closer(*io_); // read from input file via memory map const char *data = reinterpret_cast(io_->mmap()); const size_t size = io_->size(); size_t pos = 0; std::string line; // TODO: Add support for DOS EPS (C5 D0 D3 C6) // check first line const size_t firstLinePos = pos; pos = readLine(line, data, firstLinePos, size); const std::string firstLine = line; #ifdef DEBUG EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: 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(3, "EPS"); } // determine line ending style of the first line if (pos >= size) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Premature end of file after first line.\n"; #endif throw Error(write ? 21 : 14); } 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"; } else if (lineEnding == "\r") { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: Mac (CR)\n"; } else if (lineEnding == "\r\n") { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: DOS (CR LF)\n"; } else { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Line ending style: (unknown)\n"; } #endif // scan comments size_t posLanguageLevel = size; size_t posContainsXmp = size; size_t posPages = size; size_t posExiv2Version = size; size_t posExiv2Website = size; size_t posEndComments = size; size_t posPage = size; size_t posEndPageSetup = size; size_t posPageTrailer = size; size_t posEof = size; bool implicitPage = false; bool inDefaultsOrPrologOrSetup = false; bool inPageSetup = false; while (pos < posEof) { const size_t startPos = pos; pos = readLine(line, data, startPos, size); // implicit comments if (line == "%%EOF" || line == "%begin_xml_code" || !(line.size() >= 2 && line[0] == '%' && '\x21' <= line[1] && line[1] <= '\x7e')) { if (posEndComments == size) { posEndComments = startPos; #ifdef DEBUG EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit EndComments at position: " << startPos << "\n"; #endif } if (posPage == size && posEndComments != size && !inDefaultsOrPrologOrSetup && !onlyWhitespaces(line)) { posPage = startPos; implicitPage = true; #ifdef DEBUG EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: 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"; #endif } } if (line == "%%EOF" && posPageTrailer == size) { posPageTrailer = startPos; #ifdef DEBUG EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found implicit PageTrailer at position: " << startPos << "\n"; #endif } // explicit comments #ifdef DEBUG bool significantLine = true; #endif if (posEndComments == size && posLanguageLevel == size && startsWith(line, "%%LanguageLevel:")) { posLanguageLevel = startPos; } else if (posEndComments == size && posContainsXmp == size && startsWith(line, "%ADO_ContainsXMP:")) { posContainsXmp = startPos; } else if (posEndComments == size && posPages == size && startsWith(line, "%%Pages:")) { posPages = startPos; } else if (posEndComments == size && posExiv2Version == size && startsWith(line, "%Exiv2Version:")) { posExiv2Version = startPos; } else if (posEndComments == size && posExiv2Website == size && startsWith(line, "%Exiv2Website:")) { posExiv2Website = startPos; } else if (posEndComments == size && line == "%%EndComments") { posEndComments = startPos; } else if (line == "%%BeginDefaults") { inDefaultsOrPrologOrSetup = true; } else if (line == "%%EndDefaults") { inDefaultsOrPrologOrSetup = false; } else if (line == "%%BeginProlog") { inDefaultsOrPrologOrSetup = true; } else if (line == "%%EndProlog") { inDefaultsOrPrologOrSetup = false; } else if (line == "%%BeginSetup") { inDefaultsOrPrologOrSetup = true; } else if (line == "%%EndSetup") { inDefaultsOrPrologOrSetup = false; } else if (posPage == size && startsWith(line, "%%Page:")) { posPage = startPos; } else if (line == "%%BeginPageSetup") { inPageSetup = true; } else if (posEndPageSetup == size && line == "%%EndPageSetup") { inPageSetup = false; posEndPageSetup = startPos; } else if (posPageTrailer == size && line == "%%PageTrailer") { posPageTrailer = startPos; } else if (line == "%%EOF") { posEof = startPos; } else if (startsWith(line, "%%BeginDocument:")) { if (posEndPageSetup == size) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Embedded document at invalid position (before explicit or implicit EndPageSetup): " << startPos << "\n"; #endif throw Error(write ? 21 : 14); } // TODO: Add support for embedded documents! #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Embedded documents are currently not supported. Found embedded document at position: " << startPos << "\n"; #endif throw Error(write ? 21 : 14); } else if (posPage != size && 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 ? 21 : 14); } #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unable to handle multiple PostScript pages. Found second page at position: " << startPos << "\n"; #endif throw Error(write ? 21 : 14); } else if (startsWith(line, "%%Include")) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unable to handle PostScript %%Include DSC comments yet. Please provide your" " sample EPS file to the Exiv2 project: http://dev.exiv2.org/projects/exiv2\n"; #endif throw Error(write ? 21 : 14); } else { #ifdef DEBUG significantLine = false; #endif } #ifdef DEBUG if (significantLine) { EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; } #endif } // interpret comment "%ADO_ContainsXMP:" readLine(line, data, posContainsXmp, size); 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 ? 21 : 14); } std::vector > removableEmbeddings; size_t xmpPos, xmpSize; if (!containsXmp) { xmpPos = size; xmpSize = 0; } else { // search for XMP metadata findXmp(xmpPos, xmpSize, data, size, write); if (xmpSize == 0) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unable to find XMP metadata as announced at position: " << posContainsXmp << "\n"; #endif throw Error(write ? 21 : 14); } const size_t posLineAfterXmp = readLine(line, data, xmpPos + xmpSize, size); if (line != "") { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unexpected " << line.size() << " bytes of data after XMP at position: " << (xmpPos + xmpSize) << "\n"; #endif if (write) throw Error(21); } 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"; #endif } else { removableEmbeddings = findRemovableEmbeddings(data, posEof, posEndPageSetup, xmpPos, xmpSize, write); if (removableEmbeddings.empty()) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unknown XMP embedding at position: " << xmpPos << "\n"; #endif if (write) throw Error(21); } } } 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); } } 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) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Deleting XMP metadata is currently not supported.\n"; #endif throw Error(21); } // create temporary output file BasicIo::AutoPtr tempIo(io_->temporary()); assert (tempIo.get() != 0); if (!tempIo->isopen()) { #ifndef SUPPRESS_WARNINGS EXV_WARNING << "Unable to create temporary file for writing.\n"; #endif throw Error(21); } #ifdef DEBUG EXV_DEBUG << "Exiv2::EpsImage::doReadWriteMetadata: 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(posEndPageSetup); positions.push_back(posPageTrailer); positions.push_back(posEof); positions.push_back(size); if (useExistingEmbedding) { 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 size_t prevPos = 0; size_t prevSkipPos = 0; for (std::vector::const_iterator i = positions.begin(); i != positions.end(); i++) { const size_t pos = *i; if (pos == prevPos) continue; 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(21); } writeTemp(*tempIo, data + prevSkipPos, pos - prevSkipPos); const size_t posLineEnd = readLine(line, data, pos, size); size_t skipPos = pos; // add last line ending if necessary 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"; #endif } // update and complement DSC comments if (pos == posLanguageLevel && posLanguageLevel != size && !useExistingEmbedding) { if (line == "%%LanguageLevel:1" || line == "%%LanguageLevel: 1") { writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding); skipPos = posLineEnd; } } if (pos == posContainsXmp && posContainsXmp != size) { if (line != "%ADO_ContainsXMP: MainFirst") { writeTemp(*tempIo, "%ADO_ContainsXMP: MainFirst" + lineEnding); skipPos = posLineEnd; } } if (pos == posExiv2Version && posExiv2Version != size) { writeTemp(*tempIo, "%Exiv2Version: " + std::string(version()) + lineEnding); skipPos = posLineEnd; } if (pos == posExiv2Website && posExiv2Website != size) { writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); skipPos = posLineEnd; } if (pos == posEndComments) { if (posLanguageLevel == size && !useExistingEmbedding) { writeTemp(*tempIo, "%%LanguageLevel: 2" + lineEnding); } if (posContainsXmp == size) { writeTemp(*tempIo, "%ADO_ContainsXMP: MainFirst" + lineEnding); } if (posPages == size) { writeTemp(*tempIo, "%%Pages: 1" + lineEnding); } if (posExiv2Version == size) { writeTemp(*tempIo, "%Exiv2Version: " + std::string(version()) + lineEnding); } if (posExiv2Website == size) { writeTemp(*tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); } readLine(line, data, posEndComments, size); if (line != "%%EndComments") { writeTemp(*tempIo, "%%EndComments" + lineEnding); } } if (pos == posPage) { if (!startsWith(line, "%%Page:")) { writeTemp(*tempIo, "%%Page: 1 1" + lineEnding + "%%EndPageComments" + lineEnding); } } // remove unflexible embeddings for (std::vector >::const_iterator e = removableEmbeddings.begin(); e != removableEmbeddings.end(); e++) { if (pos == e->first) { skipPos = e->second; break; } } if (useExistingEmbedding) { // insert XMP metadata into existing flexible embedding if (pos == xmpPos) { writeTemp(*tempIo, xmpPacket_.data(), xmpPacket_.size()); skipPos += xmpSize; } } else { // insert XMP metadata with new flexible embedding if (pos == posEndPageSetup) { if (line != "%%EndPageSetup") { writeTemp(*tempIo, "%%BeginPageSetup" + lineEnding); } writeTemp(*tempIo, "%Exiv2BeginXMP: EndPageSetup" + lineEnding + "/currentdistillerparams where" + lineEnding + "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse" + lineEnding + "{userdict /Exiv2_pdfmark /cleartomark load put" + lineEnding + " userdict /Exiv2_metafile_pdfmark {flushfile cleartomark} bind put}" + lineEnding + "{userdict /Exiv2_pdfmark /pdfmark load put" + lineEnding + " userdict /Exiv2_metafile_pdfmark {/PUT pdfmark} bind put} ifelse" + lineEnding + "[/NamespacePush Exiv2_pdfmark" + lineEnding + "[/_objdef {Exiv2_metadata_stream} /type /stream /OBJ Exiv2_pdfmark" + lineEnding + "[{Exiv2_metadata_stream} 2 dict begin" + lineEnding + " /Type /Metadata def /Subtype /XML def currentdict end /PUT Exiv2_pdfmark" + lineEnding + "[{Exiv2_metadata_stream}" + lineEnding + " currentfile 0 (% &&end XMP packet marker&&)" + lineEnding + " /SubFileDecode filter Exiv2_metafile_pdfmark" + lineEnding); writeTemp(*tempIo, xmpPacket_.data(), xmpPacket_.size()); writeTemp(*tempIo, lineEnding + "% &&end XMP packet marker&&" + lineEnding + "[/Document 1 dict begin" + lineEnding + " /Metadata {Exiv2_metadata_stream} def currentdict end /BDC Exiv2_pdfmark" + lineEnding + "%Exiv2EndXMP" + lineEnding); if (line != "%%EndPageSetup") { writeTemp(*tempIo, "%%EndPageSetup" + lineEnding); } } if (pos == posPageTrailer) { if (pos == size || pos == posEof) { writeTemp(*tempIo, "%%PageTrailer" + lineEnding); } else { skipPos = posLineEnd; } writeTemp(*tempIo, "%Exiv2BeginXMP: PageTrailer" + lineEnding + "[/EMC Exiv2_pdfmark" + lineEnding + "[/NamespacePop Exiv2_pdfmark" + lineEnding + "%Exiv2EndXMP" + lineEnding); } } // add EOF comment if necessary if (pos == size && posEof == size) { writeTemp(*tempIo, "%%EOF" + lineEnding); } prevPos = pos; prevSkipPos = skipPos; } // 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"; } #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); } } 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; } void EpsImage::findXmp(size_t& xmpPos, size_t& xmpSize, const char* data, size_t size, bool write) { // 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 << "Exiv2::EpsImage::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 << "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) { 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; } size_t EpsImage::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; } bool EpsImage::startsWith(const std::string& s, const std::string& start) { return s.size() >= start.size() && memcmp(s.data(), start.data(), start.size()) == 0; } 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; } void EpsImage::writeTemp(BasicIo& tempIo, const char* data, size_t size) { 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); } } void EpsImage::writeTemp(BasicIo& tempIo, const std::string &data) { writeTemp(tempIo, data.data(), data.size()); } // ************************************************************************* // 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 size_t bufSize = epsDosSignature.size(); for (size_t i = 0; i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { if (bufSize < epsFirstLine[i].size()) { bufSize = epsFirstLine[i].size(); } } byte buf[bufSize]; iIo.read(buf, bufSize); if (iIo.error() || iIo.eof()) { return false; } // check for all possible (DOS) EPS signatures bool matched = (memcmp(buf, epsDosSignature.data(), epsDosSignature.size()) == 0); for (size_t i = 0; !matched && i < (sizeof epsFirstLine) / (sizeof *epsFirstLine); i++) { matched = (memcmp(buf, epsFirstLine[i].data(), epsFirstLine[i].size()) == 0); } // seek back if possible and requested if (!advance || !matched) { iIo.seek(-bufSize, BasicIo::cur); } return matched; } } // namespace Exiv2