Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kdegraphics-thumbnailers/ps/gscreator.cpp
Line
Count
Source
1
/*  This file is part of the KDE libraries
2
    SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
3
4
    Handling of EPS previews SPDX-FileCopyrightText: 2003 Philipp Hullmann <phull@gmx.de>
5
6
    SPDX-License-Identifier: LGPL-2.0-or-later
7
*/
8
9
/*  This function gets a path of a DVI, EPS, PS or PDF file and
10
    produces a PNG-Thumbnail which is stored as a QImage
11
12
    The program works as follows
13
14
    1. Test if file is a DVI file
15
16
    2. Create a child process (1), in which the
17
       file is to be changed into a PNG
18
19
    3. Child-process (1) :
20
21
    4. If file is DVI continue with 6
22
23
    5. If file is no DVI continue with 9
24
25
    6. Create another child process (2), in which the DVI is
26
       turned into PS using dvips
27
28
    7. Parent process (2) :
29
       Turn the recently created PS file into a PNG file using gs
30
31
    8. continue with 10
32
33
    9. Turn the PS,PDF or EPS file into a PNG file using gs
34
35
    10. Parent process (1)
36
        store data in a QImage
37
*/
38
39
#ifdef HAVE_CONFIG_H
40
#include <config.h>
41
#endif
42
43
44
#include <assert.h>
45
#include <ctype.h>
46
#include <stdlib.h>
47
#include <stdio.h>
48
#include <unistd.h>
49
#include <signal.h>
50
#ifdef HAVE_SYS_SELECT_H
51
#include <sys/select.h>
52
#endif
53
#include <sys/time.h>
54
#include <sys/wait.h>
55
#include <fcntl.h>
56
#include <errno.h>
57
58
#include <QColor>
59
#include <QFile>
60
#include <QImage>
61
#include <QVector>
62
63
64
#include "gscreator.h"
65
#include "dscparse.h"
66
67
#include <KPluginFactory>
68
69
0
K_PLUGIN_CLASS_WITH_JSON(GSCreator, "gsthumbnail.json")
Unexecuted instantiation: gsthumbnail_factory::tr(char const*, char const*, int)
Unexecuted instantiation: gsthumbnail_factory::~gsthumbnail_factory()
70
0
71
0
// This PS snippet will be prepended to the actual file so that only
72
0
// the first page is output.
73
0
static const char *psprolog =
74
0
    "%!PS-Adobe-3.0\n"
75
0
    "/.showpage.orig /showpage load def\n"
76
0
    "/.showpage.firstonly {\n"
77
0
    "    .showpage.orig\n"
78
0
    "    quit\n"
79
0
    "} def\n"
80
0
    "/showpage { .showpage.firstonly } def\n";
81
0
82
0
// This is the code recommended by Adobe tech note 5002 for including
83
0
// EPS files.
84
0
static const char *epsprolog =
85
0
    "%!PS-Adobe-3.0\n"
86
0
    "userdict begin /pagelevel save def /showpage { } def\n"
87
0
    "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit\n"
88
0
    "[ ] 0 setdash newpath false setoverprint false setstrokeadjust\n";
89
0
90
0
static const char * gsargs_ps[] = {
91
0
    "gs",
92
0
    "-sDEVICE=png16m",
93
0
    "-sOutputFile=-",
94
0
    "-dSAFER",
95
0
    "-dPARANOIDSAFER",
96
0
    "-dNOPAUSE",
97
0
    "-dFirstPage=1",
98
0
    "-dLastPage=1",
99
0
    "-q",
100
0
    "-",
101
0
    nullptr, // file name
102
0
    "-c",
103
0
    "showpage",
104
0
    "-c",
105
0
    "quit",
106
0
    nullptr
107
0
};
108
0
109
0
static const char * gsargs_eps[] = {
110
0
    "gs",
111
0
    "-sDEVICE=png16m",
112
0
    "-sOutputFile=-",
113
0
    "-dSAFER",
114
0
    "-dPARANOIDSAFER",
115
0
    "-dNOPAUSE",
116
0
    nullptr, // page size
117
0
    nullptr, // resolution
118
0
    "-q",
119
0
    "-",
120
0
    nullptr, // file name
121
0
    "-c",
122
0
    "pagelevel",
123
0
    "-c",
124
0
    "restore",
125
0
    "-c",
126
0
    "end",
127
0
    "-c",
128
0
    "showpage",
129
0
    "-c",
130
0
    "quit",
131
0
    nullptr
132
0
};
133
0
134
0
static const char *dvipsargs[] = {
135
0
    "dvips",
136
0
    "-n",
137
0
    "1",
138
0
    "-q",
139
0
    "-o",
140
0
    "-",
141
0
    nullptr, // file name
142
0
    nullptr
143
0
};
144
0
145
0
static bool correctDVI(const QString& filename);
146
0
147
0
148
0
namespace {
149
0
  bool got_sig_term = false;
150
0
  void handle_sigterm( int ) {
151
0
    got_sig_term = true;
152
0
  }
153
}
154
155
GSCreator::GSCreator(QObject *parent, const QVariantList &args)
156
19.2k
  : KIO::ThumbnailCreator(parent, args)
157
19.2k
{
158
19.2k
}
159
160
KIO::ThumbnailResult GSCreator::create(const KIO::ThumbnailRequest &request)
161
19.2k
{
162
19.2k
  const QString path = request.url().toLocalFile();
163
19.2k
  const int width = request.targetSize().width();
164
19.2k
  const int height = request.targetSize().height();
165
// The code in the loop (when testing whether got_sig_term got set)
166
// should read some variation of:
167
//    parentJob()->wasKilled()
168
//
169
// Unfortunatelly, that's currently impossible without breaking BIC.
170
// So we need to catch the signal ourselves.
171
// Otherwise, on certain funny PS files (for example
172
// http://www.tjhsst.edu/~Eedanaher/pslife/life.ps )
173
// gs would run forever after we were dead.
174
// #### Reconsider for KDE 4 ###
175
// (24/12/03 - luis_pedro)
176
//
177
19.2k
  typedef void ( *sighandler_t )( int );
178
  // according to linux's "man signal" the above typedef is a gnu extension
179
19.2k
  sighandler_t oldhandler = signal( SIGTERM, handle_sigterm );
180
181
19.2k
  int input[2];
182
19.2k
  int output[2];
183
19.2k
  int dvipipe[2];
184
185
19.2k
  QByteArray data(1024, '\0');
186
187
19.2k
  bool ok = false;
188
189
  // Test if file is DVI
190
19.2k
  bool no_dvi =!correctDVI(request.url().toLocalFile());
191
192
19.2k
  if (pipe(input) == -1) {
193
8.58k
    return KIO::ThumbnailResult::fail();
194
8.58k
  }
195
10.6k
  if (pipe(output) == -1) {
196
0
    close(input[0]);
197
0
    close(input[1]);
198
0
    return KIO::ThumbnailResult::fail();
199
0
  }
200
201
10.6k
  KDSC dsc;
202
10.6k
  endComments = false;
203
10.6k
  dsc.setCommentHandler(this);
204
205
10.6k
  if (no_dvi)
206
10.6k
  {
207
10.6k
    FILE* fp = fopen(QFile::encodeName(path), "r");
208
10.6k
    if (fp == nullptr) return KIO::ThumbnailResult::fail();
209
210
10.6k
    char buf[4096];
211
10.6k
    int count;
212
21.2k
    while ((count = fread(buf, sizeof(char), 4096, fp)) != 0
213
10.6k
           && !endComments) {
214
10.6k
      dsc.scanData(buf, count);
215
10.6k
    }
216
10.6k
    fclose(fp);
217
218
10.6k
    if (dsc.pjl() || dsc.ctrld()) {
219
      // this file is a mess.
220
254
      return KIO::ThumbnailResult::fail();
221
254
    }
222
10.6k
  }
223
224
10.3k
  std::unique_ptr<KDSCBBOX> bbox = dsc.bbox();
225
226
10.3k
  const bool is_encapsulated = no_dvi
227
10.3k
    && (path.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)
228
10.3k
        || path.endsWith(QLatin1String(".epsi"), Qt::CaseInsensitive))
229
0
    && bbox.get() != nullptr
230
0
    && (bbox->width() > 0)
231
0
    && (bbox->height() > 0)
232
0
    && (dsc.page_count() <= 1);
233
234
10.3k
  char translation[64] = "";
235
10.3k
  char pagesize[32] = "";
236
10.3k
  char resopt[32] = "";
237
238
10.3k
  if (is_encapsulated) {
239
    // GhostScript's rendering at the extremely low resolutions
240
    // required for thumbnails leaves something to be desired. To
241
    // get nicer images, we render to four times the required
242
    // resolution and let QImage scale the result.
243
0
    const int hres = (width * 72) / bbox->width();
244
0
    const int vres = (height * 72) / bbox->height();
245
0
    const int resolution = (hres > vres ? vres : hres) * 4;
246
0
    const int gswidth = ((bbox->urx() - bbox->llx()) * resolution) / 72;
247
0
    const int gsheight = ((bbox->ury() - bbox->lly()) * resolution) / 72;
248
249
0
    snprintf(pagesize, 31, "-g%ix%i", gswidth, gsheight);
250
0
    snprintf(resopt, 31, "-r%i", resolution);
251
0
    snprintf(translation, 63,
252
0
       " 0 %i sub 0 %i sub translate\n", bbox->llx(),
253
0
       bbox->lly());
254
0
  }
255
256
10.3k
  const CDSC_PREVIEW_TYPE previewType =
257
10.3k
    static_cast<CDSC_PREVIEW_TYPE>(dsc.preview());
258
259
10.3k
  switch (previewType) {
260
15
  case CDSC_TIFF:
261
429
  case CDSC_WMF:
262
429
  case CDSC_PICT:
263
    // FIXME: these should take precedence, since they can hold
264
    // color previews, which EPSI can't (or can it?).
265
429
     break;
266
205
  case CDSC_EPSI:
267
205
    {
268
205
      if (!bbox) {
269
205
        break;
270
205
      }
271
0
      const int xscale = bbox->width() / width;
272
0
      const int yscale = bbox->height() / height;
273
0
      const int scale = xscale < yscale ? xscale : yscale;
274
0
      if (scale == 0) break;
275
0
      if (auto result = getEPSIPreview(path,
276
0
                         dsc.beginpreview(),
277
0
                         dsc.endpreview(),
278
0
                         bbox->width() / scale,
279
0
                         bbox->height() / scale); result.isValid())
280
0
        return result;
281
      // If the preview extraction routine fails, gs is used to
282
      // create a thumbnail.
283
0
    }
284
0
    break;
285
9.74k
  case CDSC_NOPREVIEW:
286
9.74k
  default:
287
    // need to run ghostscript in these cases
288
9.74k
    break;
289
10.3k
  }
290
291
10.3k
  pid_t pid = fork();
292
10.3k
  if (pid == 0) {
293
    // Child process (1)
294
295
    //    close(STDERR_FILENO);
296
297
    // find first zero entry in gsargs and put the filename
298
    // or - (stdin) there, if DVI
299
0
    const char **gsargs = gsargs_ps;
300
0
    const char **arg = gsargs;
301
302
0
    if (no_dvi && is_encapsulated) {
303
0
      gsargs = gsargs_eps;
304
0
      arg = gsargs;
305
306
      // find first zero entry and put page size there
307
0
      while (*arg) ++arg;
308
0
      *arg = pagesize;
309
310
      // find second zero entry and put resolution there
311
0
      while (*arg) ++arg;
312
0
      *arg = resopt;
313
0
    }
314
315
    // find next zero entry and put the filename there
316
0
    QByteArray fname = QFile::encodeName( path );
317
0
    while (*arg)
318
0
      ++arg;
319
0
    if( no_dvi )
320
0
      *arg = fname.data();
321
0
    else
322
0
      *arg = "-";
323
324
    // find first zero entry in dvipsargs and put the filename there
325
0
    arg = dvipsargs;
326
0
    while (*arg)
327
0
      ++arg;
328
0
    *arg = fname.data();
329
330
0
    if( !no_dvi ){
331
0
      pipe(dvipipe);
332
0
      pid_t pid_two = fork();
333
0
      if( pid_two == 0 ){
334
  // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips
335
336
0
  close(input[0]);
337
0
  close(input[1]);
338
0
  close(output[0]);
339
0
  close(output[1]);
340
0
  close(dvipipe[0]);
341
342
0
  dup2( dvipipe[1], STDOUT_FILENO);
343
344
0
  execvp(dvipsargs[0], const_cast<char *const *>(dvipsargs));
345
0
  _exit(1);
346
0
      }
347
0
      else if(pid_two != -1){
348
0
  close(input[1]);
349
0
  close(output[0]);
350
0
  close(dvipipe[1]);
351
352
0
  dup2( dvipipe[0], STDIN_FILENO);
353
0
  dup2( output[1], STDOUT_FILENO);
354
355
0
  execvp(gsargs[0], const_cast<char *const *>(gsargs));
356
0
  _exit(1);
357
0
      }
358
0
      else{
359
  // fork() (2) failed, close these
360
0
  close(dvipipe[0]);
361
0
  close(dvipipe[1]);
362
0
      }
363
364
0
    }
365
0
    else if( no_dvi ){
366
      // Reopen stdin/stdout on the pipes and exec gs
367
0
      close(input[1]);
368
0
      close(output[0]);
369
370
0
      dup2(input[0], STDIN_FILENO);
371
0
      dup2(output[1], STDOUT_FILENO);
372
373
0
      execvp(gsargs[0], const_cast<char *const *>(gsargs));
374
0
      _exit(1);
375
0
    }
376
0
  }
377
10.3k
  else if (pid != -1) {
378
    // Parent process, write first-page-only-hack (the hack is not
379
    // used if DVI) and read the png output
380
10.3k
    close(input[0]);
381
10.3k
    close(output[1]);
382
10.3k
    const char *prolog;
383
10.3k
    if (is_encapsulated)
384
0
      prolog = epsprolog;
385
10.3k
    else
386
10.3k
      prolog = psprolog;
387
10.3k
    int count = write(input[1], prolog, strlen(prolog));
388
10.3k
    if (is_encapsulated)
389
0
      write(input[1], translation, strlen(translation));
390
391
10.3k
    close(input[1]);
392
10.3k
    if (count == static_cast<int>(strlen(prolog))) {
393
10.3k
      int offset = 0;
394
20.7k
  while (!ok) {
395
10.3k
    fd_set fds;
396
10.3k
    FD_ZERO(&fds);
397
10.3k
    FD_SET(output[0], &fds);
398
10.3k
    struct timeval tv;
399
10.3k
    tv.tv_sec = 20;
400
10.3k
    tv.tv_usec = 0;
401
402
10.3k
    got_sig_term = false;
403
10.3k
    if (select(output[0] + 1, &fds, nullptr, nullptr, &tv) <= 0) {
404
0
            if ( ( errno == EINTR || errno == EAGAIN ) && !got_sig_term ) continue;
405
0
      break; // error, timeout or master wants us to quit (SIGTERM)
406
0
          }
407
10.3k
    if (FD_ISSET(output[0], &fds)) {
408
10.3k
      count = read(output[0], data.data() + offset, 1024);
409
10.3k
      if (count == -1)
410
0
        break;
411
10.3k
      else
412
10.3k
        if (count) // prepare for next block
413
0
    {
414
0
      offset += count;
415
0
      data.resize(offset + 1024);
416
0
    }
417
10.3k
        else // got all data
418
10.3k
    {
419
10.3k
      data.resize(offset);
420
10.3k
      ok = true;
421
10.3k
    }
422
10.3k
    }
423
10.3k
  }
424
10.3k
    }
425
10.3k
    if (!ok) // error or timeout, gs probably didn't exit yet
426
0
    {
427
0
      kill(pid, SIGTERM);
428
0
    }
429
430
10.3k
    int status = 0;
431
10.3k
    int ret;
432
10.3k
    do {
433
10.3k
      ret = waitpid(pid, &status, 0);
434
10.3k
    } while (ret == -1 && errno == EINTR);
435
10.3k
    if (ret != pid || (status != 0  && status != 256) )
436
0
      ok = false;
437
10.3k
  }
438
0
  else {
439
    // fork() (1) failed, close these
440
0
    close(input[0]);
441
0
    close(input[1]);
442
0
    close(output[1]);
443
0
  }
444
10.3k
  close(output[0]);
445
446
10.3k
  QImage img;
447
10.3k
  bool loaded = img.loadFromData( data );
448
449
10.3k
  if (!loaded) {
450
    // Sometimes gs spits some warning messages before the actual image
451
    // try to skip them
452
10.3k
    const QByteArray pngHeader = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
453
10.3k
    const int pngMarkerIndex = data.indexOf(pngHeader);
454
10.3k
    if (pngMarkerIndex > 0) {
455
0
      data = data.mid(pngMarkerIndex);
456
0
      loaded = img.loadFromData( data );
457
0
    }
458
10.3k
  }
459
460
10.3k
  if ( got_sig_term &&
461
0
  oldhandler != SIG_ERR &&
462
0
  oldhandler != SIG_DFL &&
463
0
  oldhandler != SIG_IGN ) {
464
0
    oldhandler( SIGTERM ); // propagate the signal. Other things might rely on it
465
0
  }
466
10.3k
  if ( oldhandler != SIG_ERR ) signal( SIGTERM, oldhandler );
467
468
10.3k
  if (loaded) {
469
0
    return KIO::ThumbnailResult::pass(img);
470
0
  }
471
472
10.3k
  return KIO::ThumbnailResult::fail();
473
10.3k
}
474
475
void GSCreator::comment(Name name)
476
12.8k
{
477
12.8k
    switch (name) {
478
11
    case EndPreview:
479
832
    case BeginProlog:
480
1.58k
    case Page:
481
1.58k
      endComments = true;
482
1.58k
      break;
483
484
11.2k
    default:
485
11.2k
      break;
486
12.8k
    }
487
12.8k
}
488
489
// Quick function to check if the filename corresponds to a valid DVI
490
// file. Returns true if <filename> is a DVI file, false otherwise.
491
492
static bool correctDVI(const QString& filename)
493
19.2k
{
494
19.2k
  QFile f(filename);
495
19.2k
  if (!f.open(QIODevice::ReadOnly))
496
0
    return false;
497
498
19.2k
  unsigned char test[4];
499
19.2k
  if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2  )
500
19.1k
    return false;
501
502
110
  int n = f.size();
503
110
  if ( n < 134 ) // Too short for a dvi file
504
21
    return false;
505
89
  f.seek( n-4 );
506
507
89
  unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf };
508
509
89
  if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char*) trailer, 4 ) )
510
89
    return false;
511
  // We suppose now that the dvi file is complete and OK
512
0
  return true;
513
89
}
514
515
KIO::ThumbnailResult GSCreator::getEPSIPreview(const QString &path, long start, long
516
             end, int imgwidth, int imgheight)
517
0
{
518
0
  FILE *fp;
519
0
  fp = fopen(QFile::encodeName(path), "r");
520
0
  if (fp == nullptr) return KIO::ThumbnailResult::fail();
521
522
0
  const long previewsize = end - start + 1;
523
524
0
  char *buf = (char *) malloc(previewsize);
525
0
  fseek(fp, start, SEEK_SET);
526
0
  int count = fread(buf, sizeof(char), previewsize - 1, fp);
527
0
  fclose(fp);
528
0
  buf[previewsize - 1] = 0;
529
0
  if (count != previewsize - 1)
530
0
  {
531
0
    free(buf);
532
0
    return KIO::ThumbnailResult::fail();
533
0
  }
534
535
0
  QString previewstr = QString::fromLatin1(buf);
536
0
  free(buf);
537
538
0
  int offset = 0;
539
0
  while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
540
0
  int digits = 0;
541
0
  while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
542
0
  int width = previewstr.mid(offset, digits).toInt();
543
0
  offset += digits + 1;
544
0
  while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
545
0
  digits = 0;
546
0
  while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
547
0
  int height = previewstr.mid(offset, digits).toInt();
548
0
  offset += digits + 1;
549
0
  while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
550
0
  digits = 0;
551
0
  while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
552
0
  int depth = previewstr.mid(offset, digits).toInt();
553
554
  // skip over the rest of the BeginPreview comment
555
0
  while ((offset < previewsize) &&
556
0
         previewstr[offset] != QLatin1Char('\n') &&
557
0
     previewstr[offset] != QLatin1Char('\r')) offset++;
558
0
  while ((offset < previewsize) && previewstr[offset] != QLatin1Char('%')) offset++;
559
560
0
  unsigned int imagedepth;
561
0
  switch (depth) {
562
0
  case 1:
563
0
  case 2:
564
0
  case 4:
565
0
  case 8:
566
0
    imagedepth = 8;
567
0
    break;
568
0
  case 12: // valid, but not (yet) supported
569
0
  default: // illegal value
570
0
    return KIO::ThumbnailResult::fail();
571
0
  }
572
573
0
  unsigned int colors = (1U << depth);
574
0
  QImage img(width, height, QImage::Format_Indexed8);
575
0
  img.setColorCount(colors);
576
577
0
  if (imagedepth <= 8) {
578
0
    for (unsigned int gray = 0; gray < colors; gray++) {
579
0
      unsigned int grayvalue = (255U * (colors - 1 - gray)) /
580
0
  (colors - 1);
581
0
      img.setColor(gray, qRgb(grayvalue, grayvalue, grayvalue));
582
0
    }
583
0
  }
584
585
0
  const unsigned int bits_per_scan_line = width * depth;
586
0
  unsigned int bytes_per_scan_line = bits_per_scan_line / 8;
587
0
  if (bits_per_scan_line % 8) bytes_per_scan_line++;
588
0
  const unsigned int bindatabytes = height * bytes_per_scan_line;
589
0
  QVector<unsigned char> bindata(bindatabytes);
590
591
0
  for (unsigned int i = 0; i < bindatabytes; i++) {
592
0
    if (offset >= previewsize)
593
0
      return KIO::ThumbnailResult::fail();
594
595
0
    while (!isxdigit(previewstr[offset].toLatin1()) &&
596
0
     offset < previewsize)
597
0
      offset++;
598
599
0
    bool ok = false;
600
0
    bindata[i] = static_cast<unsigned char>(previewstr.mid(offset, 2).toUInt(&ok, 16));
601
0
    if (!ok)
602
0
      return KIO::ThumbnailResult::fail();
603
604
0
    offset += 2;
605
0
  }
606
607
0
  for (int scanline = 0; scanline < height; scanline++) {
608
0
    unsigned char *scanlineptr = img.scanLine(scanline);
609
610
0
    for (int pixelindex = 0; pixelindex < width; pixelindex++) {
611
0
      unsigned char pixelvalue = 0;
612
0
      const unsigned int bitoffset =
613
0
        scanline * bytes_per_scan_line * 8U + pixelindex * depth;
614
0
      for (int depthindex = 0; depthindex < depth;
615
0
           depthindex++) {
616
0
        const unsigned int byteindex = (bitoffset + depthindex) / 8U;
617
0
        const unsigned int bitindex =
618
0
          7 - ((bitoffset + depthindex) % 8U);
619
0
        const unsigned char bitvalue =
620
0
          (bindata[byteindex] & static_cast<unsigned char>(1U << bitindex)) >> bitindex;
621
0
        pixelvalue |= (bitvalue << depthindex);
622
0
      }
623
0
      scanlineptr[pixelindex] = pixelvalue;
624
0
    }
625
0
  }
626
627
0
  QImage outimg = img.convertToFormat(QImage::Format_RGB32).scaled(imgwidth, imgheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
628
629
0
  return !outimg.isNull() ? KIO::ThumbnailResult::pass(outimg) : KIO::ThumbnailResult::fail();
630
0
}
631
632
#include "gscreator.moc"