Coverage Report

Created: 2025-11-09 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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