Coverage Report

Created: 2025-08-29 06:16

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