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