/src/exiv2/src/epsimage.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | /* |
4 | | File: epsimage.cpp |
5 | | Author(s): Michael Ulbrich (mul) <mul@rentapacs.de> |
6 | | Volker Grabsch (vog) <vog@notjusthosting.com> |
7 | | History: 7-Mar-2011, vog: created |
8 | | */ |
9 | | // ***************************************************************************** |
10 | | // included header files |
11 | | #include "config.h" |
12 | | |
13 | | #include "basicio.hpp" |
14 | | #include "enforce.hpp" |
15 | | #include "epsimage.hpp" |
16 | | #include "error.hpp" |
17 | | #include "futils.hpp" |
18 | | #include "image.hpp" |
19 | | #include "version.hpp" |
20 | | |
21 | | // + standard includes |
22 | | #include <algorithm> |
23 | | #include <array> |
24 | | #include <cstring> |
25 | | #include <sstream> |
26 | | #include <string> |
27 | | |
28 | | // ***************************************************************************** |
29 | | namespace { |
30 | | using namespace Exiv2; |
31 | | using namespace Exiv2::Internal; |
32 | | |
33 | | // signature of DOS EPS |
34 | | constexpr auto dosEpsSignature = std::string_view("\xC5\xD0\xD3\xC6"); |
35 | | |
36 | | // first line of EPS |
37 | | constexpr std::array epsFirstLine{ |
38 | | std::string_view("%!PS-Adobe-3.0 EPSF-3.0"), |
39 | | std::string_view("%!PS-Adobe-3.0 EPSF-3.0 "), // OpenOffice |
40 | | std::string_view("%!PS-Adobe-3.1 EPSF-3.0"), // Illustrator |
41 | | }; |
42 | | |
43 | | // blank EPS file |
44 | | constexpr auto epsBlank = std::string_view( |
45 | | "%!PS-Adobe-3.0 EPSF-3.0\n" |
46 | | "%%BoundingBox: 0 0 0 0\n"); |
47 | | |
48 | | // list of all valid XMP headers |
49 | | constexpr std::string_view xmpHeaders[] = { |
50 | | |
51 | | // We do not enforce the trailing "?>" here, because the XMP specification |
52 | | // permits additional attributes after begin="..." and id="...". |
53 | | |
54 | | // normal headers |
55 | | "<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", |
56 | | "<?xpacket begin=\"\xef\xbb\xbf\" id='W5M0MpCehiHzreSzNTczkc9d'", |
57 | | "<?xpacket begin='\xef\xbb\xbf' id=\"W5M0MpCehiHzreSzNTczkc9d\"", |
58 | | "<?xpacket begin='\xef\xbb\xbf' id='W5M0MpCehiHzreSzNTczkc9d'", |
59 | | |
60 | | // deprecated headers (empty begin attribute, UTF-8 only) |
61 | | "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"", |
62 | | "<?xpacket begin=\"\" id='W5M0MpCehiHzreSzNTczkc9d'", |
63 | | "<?xpacket begin='' id=\"W5M0MpCehiHzreSzNTczkc9d\"", |
64 | | "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'", |
65 | | }; |
66 | | |
67 | | // list of all valid XMP trailers |
68 | | using XmpTrailer = std::pair<std::string_view, bool>; |
69 | | |
70 | | constexpr auto xmpTrailers = std::array{ |
71 | | |
72 | | // We do not enforce the trailing "?>" here, because the XMP specification |
73 | | // permits additional attributes after end="...". |
74 | | |
75 | | XmpTrailer("<?xpacket end=\"r\"", true), |
76 | | XmpTrailer("<?xpacket end='r'", true), |
77 | | XmpTrailer("<?xpacket end=\"w\"", false), |
78 | | XmpTrailer("<?xpacket end='w'", false), |
79 | | }; |
80 | | |
81 | | // closing part of all valid XMP trailers |
82 | | constexpr auto xmpTrailerEnd = std::string_view("?>"); |
83 | | |
84 | | //! Write data into temp file, taking care of errors |
85 | 50.4k | void writeTemp(BasicIo& tempIo, const byte* data, size_t size) { |
86 | 50.4k | if (size == 0) |
87 | 412 | return; |
88 | 50.0k | if (tempIo.write(data, size) != size) { |
89 | 0 | #ifndef SUPPRESS_WARNINGS |
90 | 0 | EXV_WARNING << "Failed to write to temporary file.\n"; |
91 | 0 | #endif |
92 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
93 | 0 | } |
94 | 50.0k | } |
95 | | |
96 | | //! Write data into temp file, taking care of errors |
97 | 38.6k | void writeTemp(BasicIo& tempIo, const std::string& data) { |
98 | 38.6k | writeTemp(tempIo, reinterpret_cast<const byte*>(data.data()), data.size()); |
99 | 38.6k | } |
100 | | |
101 | | //! Get the current write position of temp file, taking care of errors |
102 | 3.92k | uint32_t posTemp(const BasicIo& tempIo) { |
103 | 3.92k | const size_t pos = tempIo.tell(); |
104 | 3.92k | enforce(pos <= std::numeric_limits<uint32_t>::max(), ErrorCode::kerImageWriteFailed); |
105 | 3.92k | return static_cast<uint32_t>(pos); |
106 | 3.92k | } |
107 | | |
108 | | //! Check whether a string contains only white space characters |
109 | 12.2k | bool onlyWhitespaces(const std::string& s) { |
110 | | // According to the DSC 3.0 specification, 4.4 Parsing Rules, |
111 | | // only spaces and tabs are considered to be white space characters. |
112 | 12.2k | return s.find_first_not_of(" \t") == std::string::npos; |
113 | 12.2k | } |
114 | | |
115 | | //! Read the next line of a buffer, allow for changing line ending style |
116 | 2.19M | size_t readLine(std::string& line, const byte* data, size_t startPos, size_t size) { |
117 | 2.19M | line.clear(); |
118 | 2.19M | size_t pos = startPos; |
119 | | // step through line |
120 | 203M | while (pos < size && data[pos] != '\r' && data[pos] != '\n') { |
121 | 201M | line += data[pos]; |
122 | 201M | pos++; |
123 | 201M | } |
124 | | // skip line ending, if present |
125 | 2.19M | if (pos >= size) |
126 | 14.3k | return pos; |
127 | 2.18M | pos++; |
128 | 2.18M | if (pos >= size) |
129 | 590 | return pos; |
130 | 2.18M | if (data[pos - 1] == '\r' && data[pos] == '\n') |
131 | 8.00k | pos++; |
132 | 2.18M | return pos; |
133 | 2.18M | } |
134 | | |
135 | | //! Read the previous line of a buffer, allow for changing line ending style |
136 | 1.03k | size_t readPrevLine(std::string& line, const byte* data, size_t startPos, size_t size) { |
137 | 1.03k | line.clear(); |
138 | 1.03k | size_t pos = startPos; |
139 | 1.03k | if (pos > size) |
140 | 0 | return pos; |
141 | | // skip line ending of previous line, if present |
142 | 1.03k | if (pos <= 0) |
143 | 0 | return pos; |
144 | 1.03k | if (data[pos - 1] == '\r' || data[pos - 1] == '\n') { |
145 | 632 | pos--; |
146 | 632 | if (pos <= 0) |
147 | 0 | return pos; |
148 | 632 | if (data[pos - 1] == '\r' && data[pos] == '\n') { |
149 | 17 | pos--; |
150 | 17 | if (pos <= 0) |
151 | 0 | return pos; |
152 | 17 | } |
153 | 632 | } |
154 | | // step through previous line |
155 | 2.06M | while (pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') { |
156 | 2.06M | pos--; |
157 | 2.06M | line += data[pos]; |
158 | 2.06M | } |
159 | 1.03k | std::reverse(line.begin(), line.end()); |
160 | 1.03k | return pos; |
161 | 1.03k | } |
162 | | |
163 | | //! Find an XMP block |
164 | 16.2k | void findXmp(size_t& xmpPos, size_t& xmpSize, const byte* data, size_t startPos, size_t size, bool write) { |
165 | | // search for valid XMP header |
166 | 16.2k | xmpSize = 0; |
167 | 54.9M | for (xmpPos = startPos; xmpPos < size; xmpPos++) { |
168 | 54.9M | if (data[xmpPos] != '\x00' && data[xmpPos] != '<') |
169 | 51.0M | continue; |
170 | 30.8M | for (auto&& header : xmpHeaders) { |
171 | 30.8M | if (xmpPos + header.size() > size) |
172 | 92.1k | continue; |
173 | 30.7M | if (memcmp(data + xmpPos, header.data(), header.size()) != 0) |
174 | 30.7M | continue; |
175 | | #ifdef DEBUG |
176 | | EXV_DEBUG << "findXmp: Found XMP header at position: " << xmpPos << "\n"; |
177 | | #endif |
178 | | |
179 | | // search for valid XMP trailer |
180 | 92.4M | for (size_t trailerPos = xmpPos + header.size(); trailerPos < size; trailerPos++) { |
181 | 92.4M | if (data[xmpPos] != '\x00' && data[xmpPos] != '<') |
182 | 0 | continue; |
183 | 369M | for (const auto& [trailer, readOnly] : xmpTrailers) { |
184 | 369M | if (trailerPos + trailer.size() > size) |
185 | 7.95k | continue; |
186 | 369M | if (memcmp(data + trailerPos, trailer.data(), trailer.size()) != 0) |
187 | 369M | continue; |
188 | | #ifdef DEBUG |
189 | | EXV_DEBUG << "findXmp: Found XMP trailer at position: " << trailerPos << "\n"; |
190 | | #endif |
191 | | |
192 | 10.1k | if (readOnly) { |
193 | 23 | #ifndef SUPPRESS_WARNINGS |
194 | 23 | EXV_WARNING << "Unable to handle read-only XMP metadata yet. Please provide your " |
195 | 0 | "sample EPS file to the Exiv2 project: http://dev.exiv2.org/projects/exiv2\n"; |
196 | 23 | #endif |
197 | 23 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
198 | 23 | } |
199 | | |
200 | | // search for end of XMP trailer |
201 | 5.92M | for (size_t trailerEndPos = trailerPos + trailer.size(); trailerEndPos + xmpTrailerEnd.size() <= size; |
202 | 5.92M | trailerEndPos++) { |
203 | 5.92M | if (memcmp(data + trailerEndPos, xmpTrailerEnd.data(), xmpTrailerEnd.size()) == 0) { |
204 | 10.0k | xmpSize = (trailerEndPos + xmpTrailerEnd.size()) - xmpPos; |
205 | 10.0k | return; |
206 | 10.0k | } |
207 | 5.92M | } |
208 | 95 | #ifndef SUPPRESS_WARNINGS |
209 | 95 | EXV_WARNING << "Found XMP header but incomplete XMP trailer.\n"; |
210 | 95 | #endif |
211 | 95 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
212 | 10.1k | } |
213 | 92.4M | } |
214 | 156 | #ifndef SUPPRESS_WARNINGS |
215 | 156 | EXV_WARNING << "Found XMP header but no XMP trailer.\n"; |
216 | 156 | #endif |
217 | 156 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
218 | 10.2k | } |
219 | 3.85M | } |
220 | 16.2k | } |
221 | | |
222 | | //! Unified implementation of reading and writing EPS metadata |
223 | 7.55k | void readWriteEpsMetadata(BasicIo& io, std::string& xmpPacket, NativePreviewList& nativePreviews, bool write) { |
224 | | // open input file |
225 | 7.55k | if (io.open() != 0) { |
226 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io.path(), strError()); |
227 | 0 | } |
228 | 7.55k | IoCloser closer(io); |
229 | | |
230 | | // read from input file via memory map |
231 | 7.55k | const byte* data = io.mmap(); |
232 | | |
233 | | // default positions and sizes |
234 | 7.55k | const size_t size = io.size(); |
235 | 7.55k | size_t posEps = 0; |
236 | 7.55k | size_t posEndEps = size; |
237 | 7.55k | uint32_t posWmf = 0; |
238 | 7.55k | uint32_t sizeWmf = 0; |
239 | 7.55k | uint32_t posTiff = 0; |
240 | 7.55k | uint32_t sizeTiff = 0; |
241 | | |
242 | | // check for DOS EPS |
243 | 7.55k | const bool dosEps = |
244 | 7.55k | (size >= dosEpsSignature.size() && memcmp(data, dosEpsSignature.data(), dosEpsSignature.size()) == 0); |
245 | 7.55k | if (dosEps) { |
246 | | #ifdef DEBUG |
247 | | EXV_DEBUG << "readWriteEpsMetadata: Found DOS EPS signature\n"; |
248 | | #endif |
249 | 679 | if (size < 30) { |
250 | 4 | #ifndef SUPPRESS_WARNINGS |
251 | 4 | EXV_WARNING << "Premature end of file after DOS EPS signature.\n"; |
252 | 4 | #endif |
253 | 4 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
254 | 4 | } |
255 | 675 | posEps = getULong(data + 4, littleEndian); |
256 | 675 | posEndEps = getULong(data + 8, littleEndian) + posEps; |
257 | 675 | posWmf = getULong(data + 12, littleEndian); |
258 | 675 | sizeWmf = getULong(data + 16, littleEndian); |
259 | 675 | posTiff = getULong(data + 20, littleEndian); |
260 | 675 | sizeTiff = getULong(data + 24, littleEndian); |
261 | | #ifdef DEBUG |
262 | | EXV_DEBUG << "readWriteEpsMetadata: EPS section at position " << posEps << ", size " << (posEndEps - posEps) |
263 | | << "\n"; |
264 | | EXV_DEBUG << "readWriteEpsMetadata: WMF section at position " << posWmf << ", size " << sizeWmf << "\n"; |
265 | | EXV_DEBUG << "readWriteEpsMetadata: TIFF section at position " << posTiff << ", size " << sizeTiff << "\n"; |
266 | | #endif |
267 | 675 | if (uint16_t checksum = getUShort(data + 28, littleEndian); checksum != 0xFFFF) { |
268 | | #ifdef DEBUG |
269 | | EXV_DEBUG << "readWriteEpsMetadata: DOS EPS checksum is not FFFF\n"; |
270 | | #endif |
271 | 594 | } |
272 | 675 | if ((posWmf != 0 || sizeWmf != 0) && (posTiff != 0 || sizeTiff != 0)) { |
273 | 612 | #ifndef SUPPRESS_WARNINGS |
274 | 612 | EXV_WARNING << "DOS EPS file has both WMF and TIFF section. Only one of those is allowed.\n"; |
275 | 612 | #endif |
276 | 612 | if (write) |
277 | 14 | throw Error(ErrorCode::kerImageWriteFailed); |
278 | 612 | } |
279 | 661 | if (sizeWmf == 0 && sizeTiff == 0) { |
280 | 13 | #ifndef SUPPRESS_WARNINGS |
281 | 13 | EXV_WARNING << "DOS EPS file has neither WMF nor TIFF section. Exactly one of those is required.\n"; |
282 | 13 | #endif |
283 | 13 | if (write) |
284 | 1 | throw Error(ErrorCode::kerImageWriteFailed); |
285 | 13 | } |
286 | 660 | if (posEps < 30 || posEndEps > size) { |
287 | 401 | #ifndef SUPPRESS_WARNINGS |
288 | 401 | EXV_WARNING << "DOS EPS file has invalid position (" << posEps << ") or size (" << (posEndEps - posEps) |
289 | 0 | << ") for EPS section.\n"; |
290 | 401 | #endif |
291 | 401 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
292 | 401 | } |
293 | 259 | if (sizeWmf != 0 && (posWmf < 30 || posWmf + sizeWmf > size)) { |
294 | 205 | #ifndef SUPPRESS_WARNINGS |
295 | 205 | EXV_WARNING << "DOS EPS file has invalid position (" << posWmf << ") or size (" << sizeWmf |
296 | 0 | << ") for WMF section.\n"; |
297 | 205 | #endif |
298 | 205 | if (write) |
299 | 2 | throw Error(ErrorCode::kerImageWriteFailed); |
300 | 205 | } |
301 | 257 | if (sizeTiff != 0 && (posTiff < 30 || posTiff + sizeTiff > size)) { |
302 | 225 | #ifndef SUPPRESS_WARNINGS |
303 | 225 | EXV_WARNING << "DOS EPS file has invalid position (" << posTiff << ") or size (" << sizeTiff |
304 | 0 | << ") for TIFF section.\n"; |
305 | 225 | #endif |
306 | 225 | if (write) |
307 | 2 | throw Error(ErrorCode::kerImageWriteFailed); |
308 | 225 | } |
309 | 257 | } |
310 | | |
311 | | // check first line |
312 | 7.13k | std::string firstLine; |
313 | 7.13k | const size_t posSecondLine = readLine(firstLine, data, posEps, posEndEps); |
314 | | #ifdef DEBUG |
315 | | EXV_DEBUG << "readWriteEpsMetadata: First line: " << firstLine << "\n"; |
316 | | #endif |
317 | 7.13k | auto it = std::find(epsFirstLine.begin(), epsFirstLine.end(), firstLine); |
318 | 7.13k | if (it == epsFirstLine.end()) { |
319 | 289 | throw Error(ErrorCode::kerNotAnImage, "EPS"); |
320 | 289 | } |
321 | | |
322 | | // determine line ending style of the first line |
323 | 6.84k | if (posSecondLine >= posEndEps) { |
324 | 6 | #ifndef SUPPRESS_WARNINGS |
325 | 6 | EXV_WARNING << "Premature end of file after first line.\n"; |
326 | 6 | #endif |
327 | 6 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
328 | 6 | } |
329 | 6.83k | const std::string lineEnding(reinterpret_cast<const char*>(data + posEps + firstLine.size()), |
330 | 6.83k | posSecondLine - (posEps + firstLine.size())); |
331 | | #ifdef DEBUG |
332 | | if (lineEnding == "\n") { |
333 | | EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Unix (LF)\n"; |
334 | | } else if (lineEnding == "\r") { |
335 | | EXV_DEBUG << "readWriteEpsMetadata: Line ending style: Mac (CR)\n"; |
336 | | } else if (lineEnding == "\r\n") { |
337 | | EXV_DEBUG << "readWriteEpsMetadata: Line ending style: DOS (CR LF)\n"; |
338 | | } else { |
339 | | EXV_DEBUG << "readWriteEpsMetadata: Line ending style: (unknown)\n"; |
340 | | } |
341 | | #endif |
342 | | |
343 | | // scan comments |
344 | 6.83k | size_t posLanguageLevel = posEndEps; |
345 | 6.83k | size_t posContainsXmp = posEndEps; |
346 | 6.83k | size_t posPages = posEndEps; |
347 | 6.83k | size_t posExiv2Version = posEndEps; |
348 | 6.83k | size_t posExiv2Website = posEndEps; |
349 | 6.83k | size_t posEndComments = posEndEps; |
350 | 6.83k | size_t posAi7Thumbnail = posEndEps; |
351 | 6.83k | size_t posAi7ThumbnailEndData = posEndEps; |
352 | 6.83k | size_t posBeginPhotoshop = posEndEps; |
353 | 6.83k | size_t posEndPhotoshop = posEndEps; |
354 | 6.83k | size_t posPage = posEndEps; |
355 | 6.83k | size_t posBeginPageSetup = posEndEps; |
356 | 6.83k | size_t posEndPageSetup = posEndEps; |
357 | 6.83k | size_t posPageTrailer = posEndEps; |
358 | 6.83k | size_t posEof = posEndEps; |
359 | 6.83k | std::vector<std::pair<size_t, size_t>> removableEmbeddings; |
360 | 6.83k | size_t depth = 0; |
361 | 6.83k | const size_t maxDepth = std::numeric_limits<size_t>::max(); |
362 | 6.83k | bool illustrator8 = false; |
363 | 6.83k | bool corelDraw = false; |
364 | 6.83k | bool implicitPage = false; |
365 | 6.83k | bool implicitPageSetup = false; |
366 | 6.83k | bool implicitPageTrailer = false; |
367 | 6.83k | bool inDefaultsPreviewPrologSetup = false; |
368 | 6.83k | bool inRemovableEmbedding = false; |
369 | 6.83k | std::string removableEmbeddingEndLine; |
370 | 6.83k | size_t removableEmbeddingsWithUnmarkedTrailer = 0; |
371 | 2.17M | for (size_t pos = posEps; pos < posEof;) { |
372 | 2.16M | const size_t startPos = pos; |
373 | 2.16M | std::string line; |
374 | 2.16M | pos = readLine(line, data, startPos, posEndEps); |
375 | | #ifdef DEBUG |
376 | | bool significantLine = true; |
377 | | #endif |
378 | | // nested documents |
379 | 2.16M | if (posPage == posEndEps && (line.starts_with("%%IncludeDocument:") || line.starts_with("%%BeginDocument:"))) { |
380 | 34 | #ifndef SUPPRESS_WARNINGS |
381 | 34 | EXV_WARNING << "Nested document at invalid position: " << startPos << "\n"; |
382 | 34 | #endif |
383 | 34 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
384 | 34 | } |
385 | 2.16M | if (line.starts_with("%%BeginDocument:")) { |
386 | 393 | if (depth == maxDepth) { |
387 | 0 | #ifndef SUPPRESS_WARNINGS |
388 | 0 | EXV_WARNING << "Document too deeply nested at position: " << startPos << "\n"; |
389 | 0 | #endif |
390 | 0 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
391 | 0 | } |
392 | 393 | depth++; |
393 | 2.16M | } else if (line.starts_with("%%EndDocument")) { |
394 | 271 | if (depth == 0) { |
395 | 38 | #ifndef SUPPRESS_WARNINGS |
396 | 38 | EXV_WARNING << "Unmatched EndDocument at position: " << startPos << "\n"; |
397 | 38 | #endif |
398 | 38 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
399 | 38 | } |
400 | 233 | depth--; |
401 | 2.16M | } else { |
402 | | #ifdef DEBUG |
403 | | significantLine = false; |
404 | | #endif |
405 | 2.16M | } |
406 | | #ifdef DEBUG |
407 | | if (significantLine) { |
408 | | EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; |
409 | | } |
410 | | significantLine = true; |
411 | | #endif |
412 | 2.16M | if (depth != 0) |
413 | 2.75k | continue; |
414 | | // explicit "Begin" comments |
415 | 2.16M | if (line.starts_with("%%BeginPreview:")) { |
416 | 4.18k | inDefaultsPreviewPrologSetup = true; |
417 | 2.16M | } else if (line == "%%BeginDefaults") { |
418 | 855 | inDefaultsPreviewPrologSetup = true; |
419 | 2.15M | } else if (line == "%%BeginProlog") { |
420 | 1.09k | inDefaultsPreviewPrologSetup = true; |
421 | 2.15M | } else if (line == "%%BeginSetup") { |
422 | 1.41k | inDefaultsPreviewPrologSetup = true; |
423 | 2.15M | } else if (posPage == posEndEps && line.starts_with("%%Page:")) { |
424 | 222 | posPage = startPos; |
425 | 2.15M | } else if (posPage != posEndEps && line.starts_with("%%Page:")) { |
426 | 114 | if (implicitPage) { |
427 | 90 | #ifndef SUPPRESS_WARNINGS |
428 | 90 | EXV_WARNING << "Page at position " << startPos << " conflicts with implicit page at position: " << posPage |
429 | 0 | << "\n"; |
430 | 90 | #endif |
431 | 90 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
432 | 90 | } |
433 | 24 | #ifndef SUPPRESS_WARNINGS |
434 | 24 | EXV_WARNING << "Unable to handle multiple PostScript pages. Found second page at position: " << startPos << "\n"; |
435 | 24 | #endif |
436 | 24 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
437 | 2.15M | } else if (line == "%%BeginPageSetup") { |
438 | 611 | posBeginPageSetup = startPos; |
439 | 2.15M | } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: Before %%EndPageSetup") { |
440 | 28 | inRemovableEmbedding = true; |
441 | 28 | removableEmbeddings.emplace_back(startPos, startPos); |
442 | 28 | removableEmbeddingEndLine = "%Exiv2EndXMP"; |
443 | 2.15M | } else if (!inRemovableEmbedding && line == "%Exiv2BeginXMP: After %%PageTrailer") { |
444 | 45 | inRemovableEmbedding = true; |
445 | 45 | removableEmbeddings.emplace_back(startPos, startPos); |
446 | 45 | removableEmbeddingEndLine = "%Exiv2EndXMP"; |
447 | 2.15M | } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageSetup End \"AI11EPS\"") { |
448 | 13.7k | inRemovableEmbedding = true; |
449 | 13.7k | removableEmbeddings.emplace_back(startPos, startPos); |
450 | 13.7k | removableEmbeddingEndLine = "%ADOEndClientInjection: PageSetup End \"AI11EPS\""; |
451 | 2.14M | } else if (!inRemovableEmbedding && line == "%ADOBeginClientInjection: PageTrailer Start \"AI11EPS\"") { |
452 | 3.20k | inRemovableEmbedding = true; |
453 | 3.20k | removableEmbeddings.emplace_back(startPos, startPos); |
454 | 3.20k | removableEmbeddingEndLine = "%ADOEndClientInjection: PageTrailer Start \"AI11EPS\""; |
455 | 2.13M | } else if (!inRemovableEmbedding && line == "%begin_xml_code") { |
456 | 539 | inRemovableEmbedding = true; |
457 | 539 | removableEmbeddings.emplace_back(startPos, startPos); |
458 | 539 | removableEmbeddingEndLine = "%end_xml_code"; |
459 | 539 | removableEmbeddingsWithUnmarkedTrailer++; |
460 | 2.13M | } else { |
461 | | #ifdef DEBUG |
462 | | significantLine = false; |
463 | | #endif |
464 | 2.13M | } |
465 | | #ifdef DEBUG |
466 | | if (significantLine) { |
467 | | EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; |
468 | | } |
469 | | significantLine = true; |
470 | | #endif |
471 | | // implicit comments |
472 | 2.16M | if (line == "%%EOF" || line == "%begin_xml_code" || line.size() < 2 || line.front() != '%' || '\x21' > line[1] || |
473 | 2.16M | line[1] > '\x7e') { |
474 | 1.91M | if (posEndComments == posEndEps) { |
475 | 5.37k | posEndComments = startPos; |
476 | | #ifdef DEBUG |
477 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndComments at position: " << startPos << "\n"; |
478 | | #endif |
479 | 5.37k | } |
480 | 1.91M | } |
481 | 2.16M | if (posPage == posEndEps && posEndComments != posEndEps && !inDefaultsPreviewPrologSetup && !inRemovableEmbedding && |
482 | 2.16M | !onlyWhitespaces(line)) { |
483 | 4.65k | posPage = startPos; |
484 | 4.65k | implicitPage = true; |
485 | | #ifdef DEBUG |
486 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit Page at position: " << startPos << "\n"; |
487 | | #endif |
488 | 4.65k | } |
489 | 2.16M | if (posBeginPageSetup == posEndEps && |
490 | 2.16M | (implicitPage || (posPage != posEndEps && !inRemovableEmbedding && !line.empty() && line.front() != '%'))) { |
491 | 4.71k | posBeginPageSetup = startPos; |
492 | 4.71k | implicitPageSetup = true; |
493 | | #ifdef DEBUG |
494 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n"; |
495 | | #endif |
496 | 4.71k | } |
497 | 2.16M | if (posEndPageSetup == posEndEps && implicitPageSetup && !inRemovableEmbedding && !line.empty() && |
498 | 2.16M | line.front() != '%') { |
499 | 4.21k | posEndPageSetup = startPos; |
500 | | #ifdef DEBUG |
501 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n"; |
502 | | #endif |
503 | 4.21k | } |
504 | 2.16M | if (!line.empty() && line.front() != '%') |
505 | 1.12M | continue; // performance optimization |
506 | 1.04M | if (line == "%%EOF" || line == "%%Trailer" || line == "%%PageTrailer") { |
507 | 1.72k | if (posBeginPageSetup == posEndEps) { |
508 | 58 | posBeginPageSetup = startPos; |
509 | 58 | implicitPageSetup = true; |
510 | | #ifdef DEBUG |
511 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit BeginPageSetup at position: " << startPos << "\n"; |
512 | | #endif |
513 | 58 | } |
514 | 1.72k | if (posEndPageSetup == posEndEps) { |
515 | 93 | posEndPageSetup = startPos; |
516 | 93 | implicitPageSetup = true; |
517 | | #ifdef DEBUG |
518 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit EndPageSetup at position: " << startPos << "\n"; |
519 | | #endif |
520 | 93 | } |
521 | 1.72k | } |
522 | 1.04M | if ((line == "%%EOF" || line == "%%Trailer") && posPageTrailer == posEndEps) { |
523 | 135 | posPageTrailer = startPos; |
524 | 135 | implicitPageTrailer = true; |
525 | | #ifdef DEBUG |
526 | | EXV_DEBUG << "readWriteEpsMetadata: Found implicit PageTrailer at position: " << startPos << "\n"; |
527 | | #endif |
528 | 135 | } |
529 | | // remaining explicit comments |
530 | 1.04M | if (posEndComments == posEndEps && posLanguageLevel == posEndEps && line.starts_with("%%LanguageLevel:")) { |
531 | 165 | posLanguageLevel = startPos; |
532 | 1.04M | } else if (posEndComments == posEndEps && posContainsXmp == posEndEps && line.starts_with("%ADO_ContainsXMP:")) { |
533 | 2.51k | posContainsXmp = startPos; |
534 | 1.03M | } else if (posEndComments == posEndEps && posPages == posEndEps && line.starts_with("%%Pages:")) { |
535 | 124 | posPages = startPos; |
536 | 1.03M | } else if (posEndComments == posEndEps && posExiv2Version == posEndEps && line.starts_with("%Exiv2Version:")) { |
537 | 114 | posExiv2Version = startPos; |
538 | 1.03M | } else if (posEndComments == posEndEps && posExiv2Website == posEndEps && line.starts_with("%Exiv2Website:")) { |
539 | 144 | posExiv2Website = startPos; |
540 | 1.03M | } else if (posEndComments == posEndEps && line.starts_with("%%Creator: Adobe Illustrator") && |
541 | 1.03M | firstLine == "%!PS-Adobe-3.0 EPSF-3.0") { |
542 | 342 | illustrator8 = true; |
543 | 1.03M | } else if (posEndComments == posEndEps && line.starts_with("%AI7_Thumbnail:")) { |
544 | 1.63k | posAi7Thumbnail = startPos; |
545 | 1.03M | } else if (posEndComments == posEndEps && posAi7Thumbnail != posEndEps && posAi7ThumbnailEndData == posEndEps && |
546 | 1.03M | line == "%%EndData") { |
547 | 346 | posAi7ThumbnailEndData = startPos; |
548 | 1.03M | } else if (posEndComments == posEndEps && line == "%%EndComments") { |
549 | 9 | posEndComments = startPos; |
550 | 1.03M | } else if (inDefaultsPreviewPrologSetup && line.starts_with("%%BeginResource: procset wCorel")) { |
551 | 461 | corelDraw = true; |
552 | 1.03M | } else if (line == "%%EndPreview") { |
553 | 659 | inDefaultsPreviewPrologSetup = false; |
554 | 1.03M | } else if (line == "%%EndDefaults") { |
555 | 367 | inDefaultsPreviewPrologSetup = false; |
556 | 1.03M | } else if (line == "%%EndProlog") { |
557 | 969 | inDefaultsPreviewPrologSetup = false; |
558 | 1.03M | } else if (line == "%%EndSetup") { |
559 | 465 | inDefaultsPreviewPrologSetup = false; |
560 | 1.03M | } else if (posEndPageSetup == posEndEps && line == "%%EndPageSetup") { |
561 | 21 | posEndPageSetup = startPos; |
562 | 1.03M | } else if (posPageTrailer == posEndEps && line == "%%PageTrailer") { |
563 | 84 | posPageTrailer = startPos; |
564 | 1.03M | } else if (posBeginPhotoshop == posEndEps && line.starts_with("%BeginPhotoshop:")) { |
565 | 227 | posBeginPhotoshop = pos; |
566 | 1.03M | } else if (posBeginPhotoshop != posEndEps && posEndPhotoshop == posEndEps && line == "%EndPhotoshop") { |
567 | 48 | posEndPhotoshop = startPos; |
568 | 1.03M | } else if (inRemovableEmbedding && line == removableEmbeddingEndLine) { |
569 | 16.6k | inRemovableEmbedding = false; |
570 | 16.6k | removableEmbeddings.back().second = pos; |
571 | 1.01M | } else if (line == "%%EOF") { |
572 | 36 | posEof = startPos; |
573 | 1.01M | } else { |
574 | | #ifdef DEBUG |
575 | | significantLine = false; |
576 | | #endif |
577 | 1.01M | } |
578 | | #ifdef DEBUG |
579 | | if (significantLine) { |
580 | | EXV_DEBUG << "readWriteEpsMetadata: Found significant line \"" << line << "\" at position: " << startPos << "\n"; |
581 | | } |
582 | | #endif |
583 | 1.04M | } |
584 | | |
585 | | // check for unfinished nested documents |
586 | 6.65k | if (depth != 0) { |
587 | 39 | #ifndef SUPPRESS_WARNINGS |
588 | 39 | EXV_WARNING << "Unmatched BeginDocument (" << depth << "x)\n"; |
589 | 39 | #endif |
590 | 39 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
591 | 39 | } |
592 | | |
593 | | // look for the unmarked trailers of some removable XMP embeddings |
594 | 6.61k | size_t posXmpTrailerEnd = posEof; |
595 | 6.66k | for (size_t i = 0; i < removableEmbeddingsWithUnmarkedTrailer; i++) { |
596 | 494 | std::string line1; |
597 | 494 | const size_t posLine1 = readPrevLine(line1, data, posXmpTrailerEnd, posEndEps); |
598 | 494 | std::string line2; |
599 | 494 | const size_t posLine2 = readPrevLine(line2, data, posLine1, posEndEps); |
600 | 494 | size_t posXmpTrailer; |
601 | 494 | if (line1 == "[/EMC pdfmark") { // Exiftool style |
602 | 46 | posXmpTrailer = posLine1; |
603 | 448 | } else if (line1 == "[/NamespacePop pdfmark" && |
604 | 448 | line2 == |
605 | 30 | "[{nextImage} 1 dict begin /Metadata {photoshop_metadata_stream} def currentdict end /PUT " |
606 | 30 | "pdfmark") { // Photoshop style |
607 | 0 | posXmpTrailer = posLine2; |
608 | 448 | } else { |
609 | 448 | #ifndef SUPPRESS_WARNINGS |
610 | 448 | EXV_WARNING << "Unable to find XMP embedding trailer ending at position: " << posXmpTrailerEnd << "\n"; |
611 | 448 | #endif |
612 | 448 | if (write) |
613 | 106 | throw Error(ErrorCode::kerImageWriteFailed); |
614 | 342 | break; |
615 | 448 | } |
616 | 46 | removableEmbeddings.emplace_back(posXmpTrailer, posXmpTrailerEnd); |
617 | | #ifdef DEBUG |
618 | | auto [r, s] = removableEmbeddings.back(); |
619 | | EXV_DEBUG << "readWriteEpsMetadata: Recognized unmarked trailer of removable XMP embedding at [" << r << "," << s |
620 | | << ")\n"; |
621 | | #endif |
622 | 46 | posXmpTrailerEnd = posXmpTrailer; |
623 | 46 | } |
624 | | |
625 | | // interpret comment "%ADO_ContainsXMP:" |
626 | 6.50k | std::string line; |
627 | 6.50k | readLine(line, data, posContainsXmp, posEndEps); |
628 | 6.50k | bool containsXmp; |
629 | 6.50k | if (line == "%ADO_ContainsXMP: MainFirst" || line == "%ADO_ContainsXMP:MainFirst") { |
630 | 2.29k | containsXmp = true; |
631 | 4.21k | } else if (line.empty() || line == "%ADO_ContainsXMP: NoMain" || line == "%ADO_ContainsXMP:NoMain") { |
632 | 4.02k | containsXmp = false; |
633 | 4.02k | } else { |
634 | 183 | #ifndef SUPPRESS_WARNINGS |
635 | 183 | EXV_WARNING << "Invalid line \"" << line << "\" at position: " << posContainsXmp << "\n"; |
636 | 183 | #endif |
637 | 183 | throw Error(write ? ErrorCode::kerImageWriteFailed : ErrorCode::kerFailedToReadImageData); |
638 | 183 | } |
639 | | |
640 | 6.32k | const bool deleteXmp = (write && xmpPacket.empty()); |
641 | 6.32k | bool fixBeginXmlPacket = false; |
642 | 6.32k | bool useFlexibleEmbedding = false; |
643 | 6.32k | size_t xmpPos = posEndEps; |
644 | 6.32k | size_t xmpSize = 0; |
645 | 6.32k | if (containsXmp) { |
646 | | // search for XMP metadata |
647 | 2.29k | findXmp(xmpPos, xmpSize, data, posEps, posEndEps, write); |
648 | 2.29k | if (xmpPos == posEndEps) { |
649 | 117 | #ifndef SUPPRESS_WARNINGS |
650 | 117 | EXV_WARNING << "Unable to find XMP metadata as announced at position: " << posContainsXmp << "\n"; |
651 | 117 | #endif |
652 | 117 | } |
653 | | // check embedding of XMP metadata |
654 | 2.29k | const size_t posLineAfterXmp = readLine(line, data, xmpPos + xmpSize, posEndEps); |
655 | 2.29k | if (!line.empty()) { |
656 | 1.92k | #ifndef SUPPRESS_WARNINGS |
657 | 1.92k | EXV_WARNING << "Unexpected " << line.size() << " bytes of data after XMP at position: " << (xmpPos + xmpSize) |
658 | 0 | << "\n"; |
659 | 1.92k | #endif |
660 | 1.92k | } else if (!deleteXmp) { |
661 | 294 | readLine(line, data, posLineAfterXmp, posEndEps); |
662 | 294 | if (line == "% &&end XMP packet marker&&" || line == "% &&end XMP packet marker&&") { |
663 | 48 | useFlexibleEmbedding = true; |
664 | 48 | } |
665 | 294 | } |
666 | 2.29k | } |
667 | 6.32k | if (useFlexibleEmbedding) { |
668 | | #ifdef DEBUG |
669 | | EXV_DEBUG << "readWriteEpsMetadata: Using flexible XMP embedding\n"; |
670 | | #endif |
671 | 48 | const size_t posBeginXmlPacket = readPrevLine(line, data, xmpPos, posEndEps); |
672 | 48 | if (line.starts_with("%begin_xml_packet:")) { |
673 | | #ifdef DEBUG |
674 | | EXV_DEBUG << "readWriteEpsMetadata: XMP embedding contains %begin_xml_packet\n"; |
675 | | #endif |
676 | 3 | if (write) { |
677 | 1 | fixBeginXmlPacket = true; |
678 | 1 | xmpSize += (xmpPos - posBeginXmlPacket); |
679 | 1 | xmpPos = posBeginXmlPacket; |
680 | 1 | } |
681 | 45 | } else if (posBeginPhotoshop != posEndEps) { |
682 | 4 | #ifndef SUPPRESS_WARNINGS |
683 | 4 | EXV_WARNING << "Missing %begin_xml_packet in Photoshop EPS at position: " << xmpPos << "\n"; |
684 | 4 | #endif |
685 | 4 | if (write) |
686 | 1 | throw Error(ErrorCode::kerImageWriteFailed); |
687 | 4 | } |
688 | 48 | } |
689 | 6.32k | if (!useFlexibleEmbedding) { |
690 | | // check if there are irremovable XMP metadata blocks before EndPageSetup |
691 | 6.24k | size_t posOtherXmp = containsXmp ? xmpPos : posEps; |
692 | 6.24k | size_t sizeOtherXmp = 0; |
693 | 13.9k | for (;;) { |
694 | 13.9k | findXmp(posOtherXmp, sizeOtherXmp, data, posOtherXmp + sizeOtherXmp, posEndPageSetup, write); |
695 | 13.9k | if (posOtherXmp >= posEndPageSetup) |
696 | 5.85k | break; |
697 | 8.11k | bool isRemovableEmbedding = false; |
698 | 151k | for (const auto& [r, s] : removableEmbeddings) { |
699 | 151k | if (r <= posOtherXmp && posOtherXmp < s) { |
700 | 7.72k | isRemovableEmbedding = true; |
701 | 7.72k | break; |
702 | 7.72k | } |
703 | 151k | } |
704 | 8.11k | if (!isRemovableEmbedding) { |
705 | 152 | #ifndef SUPPRESS_WARNINGS |
706 | 152 | EXV_WARNING << "XMP metadata block is not removable at position: " << posOtherXmp << "\n"; |
707 | 152 | #endif |
708 | 152 | if (write) |
709 | 43 | throw Error(ErrorCode::kerImageWriteFailed); |
710 | 109 | break; |
711 | 152 | } |
712 | 8.11k | } |
713 | 6.24k | } |
714 | | |
715 | 6.28k | if (!write) { |
716 | | // copy XMP metadata |
717 | 4.01k | xmpPacket.assign(reinterpret_cast<const char*>(data + xmpPos), xmpSize); |
718 | | |
719 | | // native previews |
720 | 4.01k | nativePreviews.clear(); |
721 | 4.01k | if (posAi7ThumbnailEndData != posEndEps) { |
722 | 238 | NativePreview nativePreview; |
723 | 238 | std::string dummy; |
724 | 238 | std::string lineAi7Thumbnail; |
725 | 238 | const size_t posBeginData = readLine(lineAi7Thumbnail, data, posAi7Thumbnail, posEndEps); |
726 | 238 | std::istringstream lineStreamAi7Thumbnail(lineAi7Thumbnail); |
727 | 238 | lineStreamAi7Thumbnail >> dummy; |
728 | 238 | lineStreamAi7Thumbnail >> nativePreview.width_; |
729 | 238 | lineStreamAi7Thumbnail >> nativePreview.height_; |
730 | 238 | std::string depthStr; |
731 | 238 | lineStreamAi7Thumbnail >> depthStr; |
732 | 238 | std::string lineBeginData; |
733 | 238 | const size_t posAfterBeginData = readLine(lineBeginData, data, posBeginData, posEndEps); |
734 | 238 | std::istringstream lineStreamBeginData(lineBeginData); |
735 | 238 | std::string beginData; |
736 | 238 | lineStreamBeginData >> beginData; |
737 | 238 | lineStreamBeginData >> dummy; |
738 | 238 | std::string type; |
739 | 238 | lineStreamBeginData >> type; |
740 | 238 | nativePreview.position_ = static_cast<long>(posAfterBeginData); |
741 | 238 | nativePreview.size_ = static_cast<uint32_t>(posAi7ThumbnailEndData - posAfterBeginData); |
742 | 238 | nativePreview.filter_ = "hex-ai7thumbnail-pnm"; |
743 | 238 | nativePreview.mimeType_ = "image/x-portable-anymap"; |
744 | 238 | if (depthStr != "8") { |
745 | 235 | #ifndef SUPPRESS_WARNINGS |
746 | 235 | EXV_WARNING << "Unable to handle Illustrator thumbnail depth: " << depthStr << "\n"; |
747 | 235 | #endif |
748 | 235 | } else if (beginData != "%%BeginData:") { |
749 | 3 | #ifndef SUPPRESS_WARNINGS |
750 | 3 | EXV_WARNING << "Unable to handle Illustrator thumbnail data section: " << lineBeginData << "\n"; |
751 | 3 | #endif |
752 | 3 | } else if (type != "Hex") { |
753 | 0 | #ifndef SUPPRESS_WARNINGS |
754 | 0 | EXV_WARNING << "Unable to handle Illustrator thumbnail data type: " << type << "\n"; |
755 | 0 | #endif |
756 | 0 | } else { |
757 | 0 | nativePreviews.push_back(std::move(nativePreview)); |
758 | 0 | } |
759 | 238 | } |
760 | 4.01k | if (posEndPhotoshop != posEndEps) { |
761 | 32 | auto sizePhotoshop = posEndPhotoshop - posBeginPhotoshop; |
762 | 32 | NativePreview nativePreview{posBeginPhotoshop, sizePhotoshop, 0, 0, "hex-irb", "image/jpeg"}; |
763 | 32 | nativePreviews.push_back(std::move(nativePreview)); |
764 | 32 | } |
765 | 4.01k | if (sizeWmf != 0) { |
766 | 19 | NativePreview nativePreview{posWmf, sizeWmf, 0, 0, "", "image/x-wmf"}; |
767 | 19 | nativePreviews.push_back(std::move(nativePreview)); |
768 | 19 | } |
769 | 4.01k | if (sizeTiff != 0) { |
770 | 19 | NativePreview nativePreview{posTiff, sizeTiff, 0, 0, "", "image/tiff"}; |
771 | 19 | nativePreviews.push_back(std::move(nativePreview)); |
772 | 19 | } |
773 | 4.01k | } else { |
774 | | // check for Adobe Illustrator 8.0 or older |
775 | 2.26k | if (illustrator8) { |
776 | 9 | #ifndef SUPPRESS_WARNINGS |
777 | 9 | EXV_WARNING << "Unable to write to EPS files created by Adobe Illustrator 8.0 or older.\n"; |
778 | 9 | #endif |
779 | 9 | throw Error(ErrorCode::kerImageWriteFailed); |
780 | 9 | } |
781 | | |
782 | | // create temporary output file |
783 | 2.25k | MemIo tempIo; |
784 | 2.25k | if (!tempIo.isopen()) { |
785 | 0 | #ifndef SUPPRESS_WARNINGS |
786 | 0 | EXV_WARNING << "Unable to create temporary file for writing.\n"; |
787 | 0 | #endif |
788 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
789 | 0 | } |
790 | | #ifdef DEBUG |
791 | | EXV_DEBUG << "readWriteEpsMetadata: Created temporary file " << tempIo.path() << "\n"; |
792 | | #endif |
793 | | |
794 | | // sort all positions |
795 | 2.25k | std::vector<size_t> positions; |
796 | 2.25k | positions.push_back(posLanguageLevel); |
797 | 2.25k | positions.push_back(posContainsXmp); |
798 | 2.25k | positions.push_back(posPages); |
799 | 2.25k | positions.push_back(posExiv2Version); |
800 | 2.25k | positions.push_back(posExiv2Website); |
801 | 2.25k | positions.push_back(posEndComments); |
802 | 2.25k | positions.push_back(posPage); |
803 | 2.25k | positions.push_back(posBeginPageSetup); |
804 | 2.25k | positions.push_back(posEndPageSetup); |
805 | 2.25k | positions.push_back(posPageTrailer); |
806 | 2.25k | positions.push_back(posEof); |
807 | 2.25k | positions.push_back(posEndEps); |
808 | 2.25k | if (useFlexibleEmbedding) { |
809 | 17 | positions.push_back(xmpPos); |
810 | 17 | } |
811 | 6.16k | for (const auto& [r, s] : removableEmbeddings) { |
812 | 6.16k | positions.push_back(r); |
813 | 6.16k | } |
814 | 2.25k | std::sort(positions.begin(), positions.end()); |
815 | | |
816 | | // assemble result EPS document |
817 | 2.25k | if (dosEps) { |
818 | | // DOS EPS header will be written afterwards |
819 | 2 | writeTemp(tempIo, std::string(30, '\x00')); |
820 | 2 | } |
821 | 2.25k | const std::string containsXmpLine = deleteXmp ? "%ADO_ContainsXMP: NoMain" : "%ADO_ContainsXMP: MainFirst"; |
822 | 2.25k | const uint32_t posEpsNew = posTemp(tempIo); |
823 | 2.25k | size_t prevPos = posEps; |
824 | 2.25k | size_t prevSkipPos = prevPos; |
825 | 29.5k | for (const auto& pos : positions) { |
826 | 29.5k | if (pos == prevPos) |
827 | 17.7k | continue; |
828 | | #ifdef DEBUG |
829 | | EXV_DEBUG << "readWriteEpsMetadata: Writing at " << pos << "\n"; |
830 | | #endif |
831 | 11.8k | if (pos < prevSkipPos) { |
832 | 42 | #ifndef SUPPRESS_WARNINGS |
833 | 42 | EXV_WARNING << "Internal error while assembling the result EPS document: " |
834 | 0 | "Unable to continue at position " |
835 | 0 | << pos << " after skipping to position " << prevSkipPos << "\n"; |
836 | 42 | #endif |
837 | 42 | throw Error(ErrorCode::kerImageWriteFailed); |
838 | 42 | } |
839 | 11.7k | writeTemp(tempIo, data + prevSkipPos, pos - prevSkipPos); |
840 | 11.7k | const size_t posLineEnd = readLine(line, data, pos, posEndEps); |
841 | 11.7k | size_t skipPos = pos; |
842 | | // add last line ending if necessary |
843 | 11.7k | if (pos == posEndEps && pos >= 1 && data[pos - 1] != '\r' && data[pos - 1] != '\n') { |
844 | 1.78k | writeTemp(tempIo, lineEnding); |
845 | | #ifdef DEBUG |
846 | | EXV_DEBUG << "readWriteEpsMetadata: Added missing line ending of last line\n"; |
847 | | #endif |
848 | 1.78k | } |
849 | | // update and complement DSC comments |
850 | 11.7k | if (pos == posLanguageLevel && posLanguageLevel != posEndEps && !deleteXmp && !useFlexibleEmbedding && |
851 | 11.7k | (line == "%%LanguageLevel:1" || line == "%%LanguageLevel: 1")) { |
852 | 1 | writeTemp(tempIo, "%%LanguageLevel: 2" + lineEnding); |
853 | 1 | skipPos = posLineEnd; |
854 | | #ifdef DEBUG |
855 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
856 | | #endif |
857 | 1 | } |
858 | 11.7k | if (pos == posContainsXmp && posContainsXmp != posEndEps && line != containsXmpLine) { |
859 | 110 | writeTemp(tempIo, containsXmpLine + lineEnding); |
860 | 110 | skipPos = posLineEnd; |
861 | | #ifdef DEBUG |
862 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
863 | | #endif |
864 | 110 | } |
865 | 11.7k | if (pos == posExiv2Version && posExiv2Version != posEndEps) { |
866 | 43 | writeTemp(tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding); |
867 | 43 | skipPos = posLineEnd; |
868 | | #ifdef DEBUG |
869 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
870 | | #endif |
871 | 43 | } |
872 | 11.7k | if (pos == posExiv2Website && posExiv2Website != posEndEps) { |
873 | 56 | writeTemp(tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); |
874 | 56 | skipPos = posLineEnd; |
875 | | #ifdef DEBUG |
876 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
877 | | #endif |
878 | 56 | } |
879 | 11.7k | if (pos == posEndComments) { |
880 | 1.94k | if (posLanguageLevel == posEndEps && !deleteXmp && !useFlexibleEmbedding) { |
881 | 692 | writeTemp(tempIo, "%%LanguageLevel: 2" + lineEnding); |
882 | 692 | } |
883 | 1.94k | if (posContainsXmp == posEndEps) { |
884 | 1.14k | writeTemp(tempIo, containsXmpLine + lineEnding); |
885 | 1.14k | } |
886 | 1.94k | if (posPages == posEndEps) { |
887 | 1.90k | writeTemp(tempIo, "%%Pages: 1" + lineEnding); |
888 | 1.90k | } |
889 | 1.94k | if (posExiv2Version == posEndEps) { |
890 | 1.90k | writeTemp(tempIo, "%Exiv2Version: " + versionNumberHexString() + lineEnding); |
891 | 1.90k | } |
892 | 1.94k | if (posExiv2Website == posEndEps) { |
893 | 1.89k | writeTemp(tempIo, "%Exiv2Website: http://www.exiv2.org/" + lineEnding); |
894 | 1.89k | } |
895 | 1.94k | readLine(line, data, posEndComments, posEndEps); |
896 | 1.94k | if (line != "%%EndComments") { |
897 | 1.94k | writeTemp(tempIo, "%%EndComments" + lineEnding); |
898 | 1.94k | } |
899 | 1.94k | } |
900 | 11.7k | if (pos == posPage && !line.starts_with("%%Page:")) { |
901 | 1.88k | writeTemp(tempIo, "%%Page: 1 1" + lineEnding); |
902 | 1.88k | writeTemp(tempIo, "%%EndPageComments" + lineEnding); |
903 | 1.88k | } |
904 | 11.7k | if (pos == posBeginPageSetup && line != "%%BeginPageSetup") { |
905 | 1.93k | writeTemp(tempIo, "%%BeginPageSetup" + lineEnding); |
906 | 1.93k | } |
907 | | // insert XMP metadata into existing flexible embedding |
908 | 11.7k | if (useFlexibleEmbedding && pos == xmpPos) { |
909 | 17 | if (fixBeginXmlPacket) { |
910 | 1 | writeTemp(tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding); |
911 | 1 | } |
912 | 17 | writeTemp(tempIo, xmpPacket); |
913 | 17 | skipPos += xmpSize; |
914 | | #ifdef DEBUG |
915 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
916 | | #endif |
917 | 17 | } |
918 | 11.7k | if (!useFlexibleEmbedding) { |
919 | | // remove preceding embedding(s) |
920 | 443k | for (const auto& [p, s] : removableEmbeddings) { |
921 | 443k | if (pos == p) { |
922 | 3.31k | skipPos = s; |
923 | | #ifdef DEBUG |
924 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ |
925 | | << "\n"; |
926 | | #endif |
927 | 3.31k | break; |
928 | 3.31k | } |
929 | 443k | } |
930 | | // insert XMP metadata with new flexible embedding, if necessary |
931 | 8.85k | if (pos == posEndPageSetup && !deleteXmp) { |
932 | 701 | writeTemp(tempIo, "%Exiv2BeginXMP: Before %%EndPageSetup" + lineEnding); |
933 | 701 | if (corelDraw) { |
934 | 2 | writeTemp(tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding); |
935 | 2 | writeTemp(tempIo, "@rs" + lineEnding); |
936 | 2 | } |
937 | 701 | if (posBeginPhotoshop != posEndEps) { |
938 | 11 | writeTemp(tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding); |
939 | 11 | writeTemp(tempIo, "%begin_xml_code" + lineEnding); |
940 | 11 | } |
941 | 701 | writeTemp(tempIo, "/currentdistillerparams where" + lineEnding); |
942 | 701 | writeTemp(tempIo, "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse" + lineEnding); |
943 | 701 | writeTemp(tempIo, "{userdict /Exiv2_pdfmark /cleartomark load put" + lineEnding); |
944 | 701 | writeTemp(tempIo, " userdict /Exiv2_metafile_pdfmark {flushfile cleartomark} bind put}" + lineEnding); |
945 | 701 | writeTemp(tempIo, "{userdict /Exiv2_pdfmark /pdfmark load put" + lineEnding); |
946 | 701 | writeTemp(tempIo, " userdict /Exiv2_metafile_pdfmark {/PUT pdfmark} bind put} ifelse" + lineEnding); |
947 | 701 | writeTemp(tempIo, "[/NamespacePush Exiv2_pdfmark" + lineEnding); |
948 | 701 | writeTemp(tempIo, "[/_objdef {Exiv2_metadata_stream} /type /stream /OBJ Exiv2_pdfmark" + lineEnding); |
949 | 701 | writeTemp(tempIo, "[{Exiv2_metadata_stream} 2 dict begin" + lineEnding); |
950 | 701 | writeTemp(tempIo, |
951 | 701 | " /Type /Metadata def /Subtype /XML def currentdict end /PUT Exiv2_pdfmark" + lineEnding); |
952 | 701 | writeTemp(tempIo, "[{Exiv2_metadata_stream}" + lineEnding); |
953 | 701 | writeTemp(tempIo, " currentfile 0 (% &&end XMP packet marker&&)" + lineEnding); |
954 | 701 | writeTemp(tempIo, " /SubFileDecode filter Exiv2_metafile_pdfmark" + lineEnding); |
955 | 701 | if (posBeginPhotoshop != posEndEps) { |
956 | 11 | writeTemp(tempIo, |
957 | 11 | "%Exiv2Notice: The following line is needed by Photoshop. " |
958 | 11 | "Parameter must be exact size of XMP metadata." + |
959 | 11 | lineEnding); |
960 | 11 | writeTemp(tempIo, "%begin_xml_packet: " + toString(xmpPacket.size()) + lineEnding); |
961 | 11 | } |
962 | 701 | writeTemp(tempIo, xmpPacket); |
963 | 701 | writeTemp(tempIo, lineEnding); |
964 | 701 | writeTemp(tempIo, "% &&end XMP packet marker&&" + lineEnding); |
965 | 701 | writeTemp(tempIo, "[/Document 1 dict begin" + lineEnding); |
966 | 701 | writeTemp(tempIo, |
967 | 701 | " /Metadata {Exiv2_metadata_stream} def currentdict end /BDC Exiv2_pdfmark" + lineEnding); |
968 | 701 | if (posBeginPhotoshop != posEndEps) { |
969 | 11 | writeTemp(tempIo, "%Exiv2Notice: The following line is needed by Photoshop." + lineEnding); |
970 | 11 | writeTemp(tempIo, "%end_xml_code" + lineEnding); |
971 | 11 | } |
972 | 701 | if (corelDraw) { |
973 | 2 | writeTemp(tempIo, "%Exiv2Notice: The following line is needed by CorelDRAW." + lineEnding); |
974 | 2 | writeTemp(tempIo, "@sv" + lineEnding); |
975 | 2 | } |
976 | 701 | writeTemp(tempIo, "%Exiv2EndXMP" + lineEnding); |
977 | 701 | } |
978 | 8.85k | } |
979 | 11.7k | if (pos == posEndPageSetup && line != "%%EndPageSetup") { |
980 | 1.94k | writeTemp(tempIo, "%%EndPageSetup" + lineEnding); |
981 | 1.94k | } |
982 | 11.7k | if (!useFlexibleEmbedding && pos == posPageTrailer && !deleteXmp) { |
983 | 701 | if (!implicitPageTrailer) { |
984 | 700 | skipPos = posLineEnd; |
985 | | #ifdef DEBUG |
986 | | EXV_DEBUG << "readWriteEpsMetadata: Skipping to " << skipPos << " at " << __FILE__ << ":" << __LINE__ << "\n"; |
987 | | #endif |
988 | 700 | } |
989 | 701 | writeTemp(tempIo, "%%PageTrailer" + lineEnding); |
990 | 701 | writeTemp(tempIo, "%Exiv2BeginXMP: After %%PageTrailer" + lineEnding); |
991 | 701 | writeTemp(tempIo, "[/EMC Exiv2_pdfmark" + lineEnding); |
992 | 701 | writeTemp(tempIo, "[/NamespacePop Exiv2_pdfmark" + lineEnding); |
993 | 701 | writeTemp(tempIo, "%Exiv2EndXMP" + lineEnding); |
994 | 701 | } |
995 | | // add EOF comment if necessary |
996 | 11.7k | if (pos == posEndEps && posEof == posEndEps) { |
997 | 1.92k | writeTemp(tempIo, "%%EOF" + lineEnding); |
998 | 1.92k | } |
999 | 11.7k | prevPos = pos; |
1000 | 11.7k | prevSkipPos = skipPos; |
1001 | 11.7k | } |
1002 | 2.21k | const uint32_t posEndEpsNew = posTemp(tempIo); |
1003 | | #ifdef DEBUG |
1004 | | EXV_DEBUG << "readWriteEpsMetadata: New EPS size: " << (posEndEpsNew - posEpsNew) << "\n"; |
1005 | | #endif |
1006 | 2.21k | if (dosEps) { |
1007 | | // write WMF and/or TIFF section if present |
1008 | 2 | writeTemp(tempIo, data + posWmf, sizeWmf); |
1009 | 2 | writeTemp(tempIo, data + posTiff, sizeTiff); |
1010 | | #ifdef DEBUG |
1011 | | EXV_DEBUG << "readWriteEpsMetadata: New DOS EPS total size: " << posTemp(tempIo) << "\n"; |
1012 | | #endif |
1013 | | // write DOS EPS header |
1014 | 2 | if (tempIo.seek(0, BasicIo::beg) != 0) { |
1015 | 0 | #ifndef SUPPRESS_WARNINGS |
1016 | 0 | EXV_WARNING << "Internal error while seeking in temporary file.\n"; |
1017 | 0 | #endif |
1018 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
1019 | 0 | } |
1020 | 2 | byte dosEpsHeader[30]; |
1021 | 2 | dosEpsSignature.copy(reinterpret_cast<char*>(dosEpsHeader), dosEpsSignature.size()); |
1022 | 2 | ul2Data(dosEpsHeader + 4, posEpsNew, littleEndian); |
1023 | 2 | ul2Data(dosEpsHeader + 8, posEndEpsNew - posEpsNew, littleEndian); |
1024 | 2 | ul2Data(dosEpsHeader + 12, sizeWmf == 0 ? 0 : posEndEpsNew, littleEndian); |
1025 | 2 | ul2Data(dosEpsHeader + 16, sizeWmf, littleEndian); |
1026 | 2 | ul2Data(dosEpsHeader + 20, sizeTiff == 0 ? 0 : posEndEpsNew + sizeWmf, littleEndian); |
1027 | 2 | ul2Data(dosEpsHeader + 24, sizeTiff, littleEndian); |
1028 | 2 | us2Data(dosEpsHeader + 28, 0xFFFF, littleEndian); |
1029 | 2 | writeTemp(tempIo, dosEpsHeader, sizeof(dosEpsHeader)); |
1030 | 2 | } |
1031 | | |
1032 | | // copy temporary file to real output file |
1033 | 2.21k | io.close(); |
1034 | 2.21k | io.transfer(tempIo); |
1035 | 2.21k | } |
1036 | 6.28k | } |
1037 | | |
1038 | | } // namespace |
1039 | | |
1040 | | // ***************************************************************************** |
1041 | | // class member definitions |
1042 | | namespace Exiv2 { |
1043 | 5.39k | EpsImage::EpsImage(BasicIo::UniquePtr io, bool create) : Image(ImageType::eps, mdXmp, std::move(io)) { |
1044 | | // LogMsg::setLevel(LogMsg::debug); |
1045 | 5.39k | if (create && io_->open() == 0) { |
1046 | | #ifdef DEBUG |
1047 | | EXV_DEBUG << "Exiv2::EpsImage:: Creating blank EPS image\n"; |
1048 | | #endif |
1049 | 0 | IoCloser closer(*io_); |
1050 | 0 | if (io_->write(reinterpret_cast<const byte*>(epsBlank.data()), epsBlank.size()) != epsBlank.size()) { |
1051 | 0 | #ifndef SUPPRESS_WARNINGS |
1052 | 0 | EXV_WARNING << "Failed to write blank EPS image.\n"; |
1053 | 0 | #endif |
1054 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
1055 | 0 | } |
1056 | 0 | } |
1057 | 5.39k | } |
1058 | | |
1059 | 0 | std::string EpsImage::mimeType() const { |
1060 | 0 | return "application/postscript"; |
1061 | 0 | } |
1062 | | |
1063 | 0 | void EpsImage::setComment(const std::string&) { |
1064 | 0 | throw Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "EPS"); |
1065 | 0 | } |
1066 | | |
1067 | 5.39k | void EpsImage::readMetadata() { |
1068 | | #ifdef DEBUG |
1069 | | EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Reading EPS file " << io_->path() << "\n"; |
1070 | | #endif |
1071 | | |
1072 | | // read metadata |
1073 | 5.39k | readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ false); |
1074 | | |
1075 | | // decode XMP metadata |
1076 | 5.39k | if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_) > 1) { |
1077 | 486 | #ifndef SUPPRESS_WARNINGS |
1078 | 486 | EXV_WARNING << "Failed to decode XMP metadata.\n"; |
1079 | 486 | #endif |
1080 | 486 | throw Error(ErrorCode::kerFailedToReadImageData); |
1081 | 486 | } |
1082 | | |
1083 | | #ifdef DEBUG |
1084 | | EXV_DEBUG << "Exiv2::EpsImage::readMetadata: Finished reading EPS file " << io_->path() << "\n"; |
1085 | | #endif |
1086 | 5.39k | } |
1087 | | |
1088 | 2.19k | void EpsImage::writeMetadata() { |
1089 | | #ifdef DEBUG |
1090 | | EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Writing EPS file " << io_->path() << "\n"; |
1091 | | #endif |
1092 | | |
1093 | | // encode XMP metadata if necessary |
1094 | 2.19k | if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) { |
1095 | 31 | #ifndef SUPPRESS_WARNINGS |
1096 | 31 | EXV_WARNING << "Failed to encode XMP metadata.\n"; |
1097 | 31 | #endif |
1098 | 31 | throw Error(ErrorCode::kerImageWriteFailed); |
1099 | 31 | } |
1100 | | |
1101 | | // write metadata |
1102 | 2.16k | readWriteEpsMetadata(*io_, xmpPacket_, nativePreviews_, /* write = */ true); |
1103 | | |
1104 | | #ifdef DEBUG |
1105 | | EXV_DEBUG << "Exiv2::EpsImage::writeMetadata: Finished writing EPS file " << io_->path() << "\n"; |
1106 | | #endif |
1107 | 2.16k | } |
1108 | | |
1109 | | // ************************************************************************* |
1110 | | // free functions |
1111 | 5.39k | Image::UniquePtr newEpsInstance(BasicIo::UniquePtr io, bool create) { |
1112 | 5.39k | auto image = std::make_unique<EpsImage>(std::move(io), create); |
1113 | 5.39k | if (!image->good()) { |
1114 | 0 | return nullptr; |
1115 | 0 | } |
1116 | 5.39k | return image; |
1117 | 5.39k | } |
1118 | | |
1119 | 38.8k | bool isEpsType(BasicIo& iIo, bool advance) { |
1120 | | // read as many bytes as needed for the longest (DOS) EPS signature |
1121 | 38.8k | constexpr auto bufSize = [] { |
1122 | 38.8k | auto f = [](const auto& a, const auto& b) { return a.size() < b.size(); }; |
1123 | 38.8k | return std::max_element(epsFirstLine.begin(), epsFirstLine.end(), f)->size(); |
1124 | 38.8k | }(); |
1125 | 38.8k | const size_t restore = iIo.tell(); // save |
1126 | 38.8k | DataBuf buf = iIo.read(bufSize); |
1127 | 38.8k | if (iIo.error() || buf.size() != bufSize) { |
1128 | 0 | iIo.seek(restore, BasicIo::beg); |
1129 | 0 | return false; |
1130 | 0 | } |
1131 | | // check for all possible (DOS) EPS signatures |
1132 | 38.8k | bool matched = (buf.cmpBytes(0, dosEpsSignature.data(), dosEpsSignature.size()) == 0); |
1133 | 38.8k | if (!matched) { |
1134 | 107k | for (auto&& eps : epsFirstLine) { |
1135 | 107k | if (buf.cmpBytes(0, eps.data(), eps.size()) == 0) { |
1136 | 9.47k | matched = true; |
1137 | 9.47k | break; |
1138 | 9.47k | } |
1139 | 107k | } |
1140 | 37.5k | } |
1141 | | // seek back if possible and requested |
1142 | 38.8k | if (!advance || !matched) { |
1143 | 38.8k | iIo.seek(restore, BasicIo::beg); |
1144 | 38.8k | } |
1145 | 38.8k | return matched; |
1146 | 38.8k | } |
1147 | | |
1148 | | } // namespace Exiv2 |