1 /*
2  *
3 
4  Context Free Design Grammar - version 0.1
5 
6  Copyright (C) 2005 Chris Coyne - ccoyne77@gmail.com
7  Copyright (C) 2005-2008 Mark Lentczner - markl@glyphic.com
8  Copyright (C) 2008-2014 John Horigan - john@glyphic.com
9 
10  [Send me anything cool you make with it or of it.]
11 
12  This program is free software; you can redistribute it and/or
13  modify it under the terms of the GNU General Public License as published
14  by the Free Software Foundation; either version 2 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program; if not, write to the Free Software
24  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25  02111-1307  USA
26 
27  *
28  */
29 
30 #define NOMINMAX 1
31 #include <iostream>
32 #include <sstream>
33 #include <fstream>
34 #include <time.h>
35 #include "args.hxx"
36 #include <cstdlib>
37 #include <cstdio>
38 #include "cfdg.h"
39 #include "variation.h"
40 #ifdef _WIN32
41 #include "WinPngCanvas.h"
42 #else
43 #include "pngCanvas.h"
44 #endif
45 #include "SVGCanvas.h"
46 #include "ffCanvas.h"
47 #include "commandLineSystem.h"
48 #include "version.h"
49 #include "Rand64.h"
50 #include "makeCFfilename.h"
51 #include <cassert>
52 #include <memory>
53 #include <string>
54 #include "astexpression.h"
55 #include "prettyint.h"
56 
57 using std::string;
58 using std::cerr;
59 using std::endl;
60 
61 void setupTimer(std::shared_ptr<Renderer>& renderer);
62 void cleanupTimer();
63 
64 std::ostream* myCout = &cerr;
65 
66 static std::weak_ptr<Renderer> gRenderer;
67 
processInterrupt()68 static bool processInterrupt()
69 {
70     auto TheRenderer = gRenderer.lock();
71     if (!TheRenderer) return false;
72 
73     if (!TheRenderer->requestFinishUp) {
74         TheRenderer->requestFinishUp = true;
75         cerr << endl << "Render interrupted, drawing current shapes" << endl;
76     } else if (!TheRenderer->requestStop) {
77         TheRenderer->requestStop = true;
78         cerr << endl << "Render interrupted, skipping drawing, cleaning up temporary files" << endl;
79     } else {
80         exit(9);
81     }
82     return true;
83 }
84 
85 #ifdef _WIN32
86 #include <Windows.h>
87 
CtrlHandler(DWORD ctrlType)88 BOOL CtrlHandler(DWORD ctrlType)
89 {
90     if (ctrlType != CTRL_C_EVENT && ctrlType != CTRL_BREAK_EVENT)
91         return FALSE;
92     return processInterrupt();
93 }
94 #else
95 #include <csignal>
96 
termination_handler(int)97 void termination_handler(int)
98 {
99     processInterrupt();
100 }
101 #endif
102 
103 
104 struct options {
105     enum OutputFormat { PNGfile = 0, SVGfile = 1, MOVfile = 2, BMPfile = 3, JSONfile = 4 };
106     int   width;
107     int   height;
108     int   widthMult;
109     int   heightMult;
110     int   maxShapes;
111     double minSize;
112     double borderSize;
113     std::string definitions;
114 
115     int   variation;
116     bool  crop;
117     bool  check;
118     int   animationFrames;
119     int   animationTime;
120     int   animationFPS;
121     bool  animationZoom;
122     int   animateFrame;
123     ffCanvas::QTcodec animationCodec;
124 
125     std::string input;
126     std::string output;
127     OutputFormat format;
128     std::string displayExec;
129 
130     bool quiet;
131     bool outputTime;
132     bool outputStdout;
133     bool outputTemp;
134     bool outputWallpaper;
135     bool paramTest;
136     bool deleteTemps;
137 
optionsoptions138     options()
139     : width(500), height(500), widthMult(1), heightMult(1), maxShapes(0),
140       minSize(0.3F), borderSize(2.0F), variation(-1), crop(false), check(false),
141       animationFrames(0), animationTime(0), animationFPS(15), animationZoom(false),
142       animateFrame(0), animationCodec(ffCanvas::H264), format(PNGfile), quiet(false),
143       outputTime(false), outputStdout(false), outputTemp(false), outputWallpaper(false),
144       paramTest(false), deleteTemps(false)
145     { }
146 };
147 
148 int
intArg2(const std::string & arg,const std::string & sstr,int & x,int & y)149 intArg2(const std::string& arg, const std::string& sstr, int& x, int& y)
150 {
151     char* end;
152     const char* str = sstr.c_str();
153     long int v = -1;
154 
155     v = strtol(str, &end, 10);
156     if (end == str || v <= 0) {
157         cerr << "Option " << arg << " takes a positive integer argument or a pair of positive integers (e.g., 500x300)" << endl;
158         return 0;
159     }
160     x = static_cast<int>(v);
161     if (*end == 'x') {
162         str = end + 1;
163         v = strtol(str, &end, 10);
164         if (end == str || v <= 0) {
165             cerr << "Option -" << arg << " takes a positive integer argument or a pair of positive integers (e.g., 500x300)" << endl;
166             return 0;
167         }
168         y = static_cast<int>(v);
169     } else {
170         y = x;
171         return 1;
172     }
173     return 2;
174 }
175 
176 void
processCommandLine(int argc,char * argv[],options & opt)177 processCommandLine(int argc, char* argv[], options& opt)
178 {
179     std::ostringstream name, epilog;
180     name << APP_NAME(argv[0]) << " - " << APP_VERSION() << "(v" << APP_BUILD()
181          << ") - Context Free Design Grammar";
182     epilog << "If '-' is specified for the CFDG FILE then the input cfdg file is piped "
183               "from standard input. If the output file name is omitted and the "
184            << APP_OPTCHAR() << "o option and the " << APP_OPTCHAR()
185            << "C option are not used then the output will be sent to stdout.";
186     args::ArgumentParser parser(name.str(), epilog.str());
187     parser.ShortPrefix(APP_OPTCHAR());      // Set Windows/posix command syntax
188     parser.LongPrefix(APP_OPTLONG());
189     parser.LongSeparator(APP_OPTSEP());
190     parser.Terminator(APP_OPTLONG());
191     parser.Prog(APP_NAME(argv[0]));         // Set app name minus path
192     args::HelpFlag help(parser, "HELP", "Show this help menu.", {'?', "help"});
193     args::Flag version(parser, "version", "Output version and quit.", {"version"});
194     args::ValueFlag<int> width(parser, "WIDTH", "Output width", {'w', "width"}, 500);
195     args::ValueFlag<int> height(parser, "HEIGHT", "Output height", {'h', "height"}, 500);
196     args::ValueFlag<string> size(parser, "SIZE or WIDTHxHEIGHT",
197                                  "Set output size to SIZExSIZE or WIDTHxHEIGHT",
198                                  {'s', "size"}, "");
199     args::ValueFlag<string> tile(parser, "SIZE or WIDTHxHEIGHT",
200                                  "Multiply output by SIZExSIZE or WIDTHxHEIGHT",
201                                  {'T', "tile"}, "");
202     args::ValueFlag<int> maxShapes(parser, "MAXSHAPES",
203                                    "Maximum number of shapes", {'m', "maxshapes"}, 0);
204     args::ValueFlag<double> minSize(parser, "MINIMUM SIZE",
205                                     "Minimum size of shapes in pixels/mm (default 0.3)",
206                                     {'x', "minimumsize"}, 0.3);
207     args::ValueFlag<double> borderSize(parser, "BORDER SIZE", "Border size [-1,2]: "
208                                        "-1=-8 pixel border, 0=no border, 1=8 pixel "
209                                        "border, 2=variable-sized border",
210                                        {'b', "bordersize"}, 2.0);
211     args::ValueFlag<string> variation(parser, "VARIATION",
212         "Set the variation code (default is random)", {'v', "variation"}, "");
213     args::ValueFlagList<string> definition(parser, "NAME=VALUE",
214         "Define a variable, configuration, or function. Overrides definitions in the input file.", {'D'});
215     args::ValueFlag<string> outputFileTemplate(parser, "NAME TEMPLATE",
216         "Set the output file name, supports variable expansion %f expands to the "
217         "animation frame number, %v and %V expands to the variation code in lower "
218         "or upper case, %% expands to %", {'o', "outputtemplate"}, "");
219     args::ValueFlag<string> animation(parser, "NUM or TIMExFPS",
220         "Generate NUM animation frames at 15fps or TIMExFPS animation frames",
221         {'a',"animate"}, "");
222     args::ValueFlag<int> frame(parser, "FRAME", "Animate a particular frame", {'f', "frame"}, 0);
223     args::Flag zoom(parser, "zoom", "Zoom out during animation", {'z', "zoom"});
224     args::Flag makeSVG(parser, "SVG", "Generate SVG output (not allowed for animation)",
225                        {'V', "svg"});
226     args::Flag makeJSON(parser, "JSON", "Generate JSON output of parsed cfdg file",
227                        {'J', "json"});
228     args::Flag makeQT(parser, "quicktime", "Make QuickTime output", {'Q', "quicktime"});
229     args::Flag makeProRes(parser, "ProRes", "Use ProRes codec for QuickTime output", { "prores" });
230 #ifdef _WIN32
231     args::Flag wallpaper(parser, "wallpaper", "Generate desktop wallpaper output",
232                          {'W', "wallpaper"});
233 #else
234     const bool wallpaper = false;
235 #endif
236     args::ValueFlag<string> display(parser, "display executable", "Display output with specified program", {"display"}, "");
237     args::Flag crop(parser, "crop", "Crop output", {'c', "crop"});
238     args::Flag quiet(parser, "quiet", "Quiet mode, suppress non-error output", {'q', "quiet"});
239     args::Flag check(parser, "check", "Check syntax of cfdg file and exit", {'C', "check"});
240     args::Flag timer(parser, "time", "Output the time taken to render the cfdg file", {'t', "time"});
241     args::Flag paramDebug(parser, "param debug", "Parameter allocation debug, test "
242         "whether all the parameter blocks were cleaned up", {'P', "paramdebug"});
243     args::Flag cleanup(parser, "cleanup", "Delete old temporary files", {'d', "cleanup"});
244     args::Positional<std::string> inputFile(parser, "CFDG FILE", "Input cfdg file", "");
245     args::Positional<std::string> outputFile(parser, "OUTPUT FILE", "Output image file", "");
246 
247     auto bailout = [&](const char * msg) {
248         if (msg && *msg)
249             cerr << msg << endl;
250         cerr << parser;
251         exit(2);
252     };
253 
254     try {
255         if (!parser.ParseCLI(argc, argv))
256             bailout("Too many arguments.");
257     } catch (args::Help&) {
258         std::cout << parser;
259         exit(0);
260     } catch (args::Error& e) {
261         bailout(e.what());
262     }
263 
264     if (version) {
265         std::cout << name.str() << endl;
266         exit(0);
267     }
268     if (width) opt.width = args::get(width);
269     if (height) opt.height = args::get(height);
270     if (opt.width < 10 || opt.height < 10)
271         bailout("Minimum output dimensions are 10 pixels.");
272     if (size && intArg2(parser.ShortPrefix() + 's', args::get(size), opt.width, opt.height) < 1)
273         bailout(nullptr);
274     if (tile && intArg2(parser.ShortPrefix() + 'T', args::get(tile), opt.widthMult, opt.heightMult) < 1)
275         bailout(nullptr);
276     if (maxShapes) {
277         opt.maxShapes = args::get(maxShapes);
278         if (opt.maxShapes < 1)
279             bailout("Must specify at least one shape.");
280     }
281     if (minSize) opt.minSize = args::get(minSize);
282     if (borderSize) {
283         opt.borderSize = args::get(borderSize);
284         if (opt.borderSize < -1.0 || opt.borderSize > 2.0)
285             bailout("Border size must be between -1 and 2");
286     }
287     if (variation) {
288         opt.variation = Variation::fromString(args::get(variation).c_str());
289         if (opt.variation == -1)
290             bailout("Error parsing variation");
291     }
292     if (definition) {
293         auto defs = definition.Get();
294         for (auto&& def: defs) {
295             auto equal = def.find("=");
296             if (equal == std::string::npos || equal == 0 || equal == def.length() - 1)
297                 bailout("Definitions must be of the form -Dname=value");
298             opt.definitions.append(def);
299             opt.definitions += ' ';
300         }
301     }
302     if (outputFileTemplate) opt.output = args::get(outputFileTemplate);
303     if (display) opt.displayExec = args::get(display);
304     if (animation) {
305         if (makeSVG) bailout("Animation cannot output to SVG files.");
306         if (crop) bailout("Animation cannot output cropped files.");
307         if (makeQT) opt.format = options::MOVfile;
308         if (makeProRes) opt.animationCodec = ffCanvas::ProRes;
309         opt.animationZoom = zoom;
310         int fps = 15, time = 0;
311         switch (intArg2(parser.ShortPrefix() + 'a', args::get(animation), time, fps)) {
312             case 2:
313                 opt.animationTime = time;
314                 opt.animationFPS = fps;
315                 opt.animationFrames = time * fps;
316                 break;
317             case 1:
318                 opt.animationTime = time / opt.animationFPS;
319                 opt.animationFrames = time;
320                 break;
321             default:
322                 bailout(nullptr);
323         }
324         if (display && !makeQT)
325             bailout("Only QuickTime animations can be displayed.");
326         if (makeQT && !ffCanvas::Available())
327             bailout("FFmpeg DLLs not found, QuickTime output is unavailable.");
328         if (makeProRes && !makeQT)
329             bailout("ProRes codec only available with QuickTime output.");
330         if (frame) {
331             if (makeQT)
332                 bailout("Single frame animation only outputs PNG files.");
333             opt.animateFrame = args::get(frame);
334             if (opt.animateFrame < 1)
335                 bailout("Animation frame must be a positive integer.");
336             if (opt.animateFrame > opt.animationFrames)
337                 bailout("Animation frame is after the end of the animation.");
338         }
339     } else {
340         if (makeQT)
341             bailout("QuickTime output is only available when animating.");
342         if (zoom)
343             bailout("Zoomed output is only available when animating.");
344         if (frame)
345             bailout("Animation frame can only be rendered when animating.");
346     }
347     if (makeSVG) opt.format = options::SVGfile;
348     if (wallpaper) {
349         if (makeSVG || animation || crop)
350             bailout("Wallpaper output must be uncropped, not animated, and not SVG.");
351         opt.format = options::BMPfile;
352         opt.outputWallpaper = true;
353     }
354     if (makeJSON) opt.format = options::JSONfile;
355     opt.crop = crop;
356     opt.check = check;
357     opt.quiet = quiet;
358     opt.outputTime = timer;
359     opt.paramTest = paramDebug;
360     opt.deleteTemps = cleanup;
361     if (quiet && cleanup)
362         bailout("Cannot clean up temporary files quietly.");
363     if (inputFile) opt.input = args::get(inputFile);
364     if (outputFile) {
365         if (!opt.output.empty())
366             bailout("Two output files specified.");
367 
368         // If a static output file name is provided then generate an output
369         // file name format string by escaping any '%' characters. If this is
370         // an animation run with PNG output then add "_%f" before the extension.
371         opt.output.reserve(args::get(outputFile).length());
372         for (char c: args::get(outputFile)) {
373             opt.output.append(c == '%' ? 2 : 1, c);
374         }
375         if (opt.animationFrames && opt.animateFrame == 0 && opt.format != options::MOVfile) {
376             std::size_t ext = opt.output.find_last_of('.');
377             std::size_t dir = opt.output.find_last_of(APP_DIRCHAR());
378             if (ext != string::npos && (dir == string::npos || ext > dir)) {
379                 opt.output.insert(ext, "_%f");
380             } else {
381                 opt.output.append("_%f");
382             }
383         }
384     }
385     if (!inputFile && !cleanup)
386         bailout("Missing input file.");
387     if (!outputFile && !outputFileTemplate && display)
388         opt.outputTemp = true;
389     if ((!outputFile || opt.output == "-") && !outputFileTemplate && !check && !opt.outputTemp) {
390         opt.outputStdout = true;
391         opt.output = "-";
392         opt.quiet = true;
393     }
394 }
395 
396 class nullstreambuf : public std::streambuf
397 {
398 protected:
overflow(int c)399     int overflow(int c) { return c; }
400 };
401 class nullostream : public std::ostream
402 {
403 public:
nullostream()404     nullostream() : std::ostream(new nullstreambuf()) {}
405 };
406 
407 static nullostream cnull;
408 
409 namespace {
410     struct OstreamCloser
411     {
operator ()__anon394df9df0211::OstreamCloser412         void operator()(std::ostream* ptr) const {
413             if (ptr != &std::cout)
414                 delete ptr;        // Not called if nullptr
415         }
416     };
417 }
418 
419 
main(int argc,char * argv[])420 int main (int argc, char* argv[]) {
421     options opts;
422     int var = Variation::random(6);
423 
424 #ifdef _WIN32
425     SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
426 #else
427     struct sigaction new_action, old_action;
428     new_action.sa_handler = termination_handler;
429     sigemptyset(&new_action.sa_mask);
430     new_action.sa_flags = 0;
431 
432     sigaction(SIGINT, nullptr, &old_action);
433     if (old_action.sa_handler != SIG_IGN)
434         sigaction(SIGINT, &new_action, nullptr);
435 #endif
436 
437     processCommandLine(argc, argv, opts);
438 
439     if (opts.quiet) myCout = &cnull;
440 
441     clock_t startTime = clock();
442     clock_t fromTime = startTime;
443     clock_t clocksPerMsec = CLOCKS_PER_SEC / 1000;
444 
445     if (opts.variation < 0) opts.variation = var;
446     std::string code = Variation::toString(opts.variation, false);
447 
448     CommandLineSystem system(opts.quiet);
449 
450     if (!opts.quiet || opts.deleteTemps) {
451         auto temps = system.findTempFiles();
452         if (temps.empty()) {
453             if (opts.deleteTemps)
454                 cerr << "No temporary files found." << endl;
455         } else {
456             if (opts.deleteTemps) {
457                 cerr << "Old temporary files deleted:\n";
458                 for (auto&& temp : temps)
459                     if (system.deleteTempFile(temp))
460                         fprintf(stderr, "Failed to delete: " FileFormat "\n", temp.c_str());
461                     else
462                         fprintf(stderr, FileFormat "\n", temp.c_str());
463                 cerr << endl;
464             } else {
465                 cerr << "Old temporary files found:\n";
466                 for (auto&& temp: temps)
467                     fprintf(stderr, FileFormat "\n", temp.c_str());
468                 cerr << endl;
469             }
470         }
471 
472         if (opts.input.empty()) exit(0);
473     }
474 
475     AST::ASTfunction::RandStaticIsConst = opts.format != options::JSONfile;
476     cfdg_ptr myDesign = CFDG::ParseFile(opts.input.c_str(), &system,
477                                         opts.variation, opts.definitions);
478     if (!myDesign) return 3;
479     if (opts.check) return 0;
480     if (opts.format == options::JSONfile) {
481         std::unique_ptr<std::ostream, OstreamCloser> out(nullptr);
482         if (opts.outputStdout)
483             out.reset(&std::cout);
484         else
485             out.reset(new std::ofstream(opts.output));
486         myDesign->serialize(*out);
487         return 0;
488     }
489     if (opts.widthMult != 1 || opts.heightMult != 1) {
490         if (!myDesign->isTiled() && !myDesign->isFrieze()) {
491             cerr << "Tiled output multiplication only allowed for tiled or frieze designs." << endl;
492             return 6;
493         }
494         if (opts.format != options::PNGfile && opts.format != options::BMPfile) {
495             cerr << "Tiled output multiplication only allowed for PNG output." << endl;
496             return 6;
497         }
498     }
499 
500     bool useRGBA = myDesign->usesColor;
501     aggCanvas::PixelFormat pixfmt = aggCanvas::SuggestPixelFormat(myDesign.get());
502     bool use16bit = (pixfmt & aggCanvas::Has_16bit_Color) != 0;
503     bool usecustom = (pixfmt & aggCanvas::Has_Custom_Blend) != 0;
504     const char* fmtnames[4] = { "PNG image", "SVG vector output", "Quicktime movie", "Wallpaper BMP image" };
505 
506     *myCout << "Generating " << (use16bit ? "16bit " : "8bit ")
507         << (useRGBA ? "color" : "gray-scale")
508         << (usecustom ? ", custom blend" : "")
509         << ' ' << fmtnames[opts.format]
510         << ", variation "
511         << code << "..." << endl;
512 
513     std::string actualFileName;
514 
515     { // Scope for canvas & renderer
516     std::unique_ptr<pngCanvas> png;
517     std::unique_ptr<SVGCanvas> svg;
518     std::unique_ptr<ffCanvas>  mov;
519     Canvas* myCanvas = nullptr;
520 
521     std::shared_ptr<Renderer> TheRenderer(myDesign->renderer(myDesign,
522                                           opts.width, opts.height, opts.minSize,
523                                           opts.variation, opts.borderSize));
524 
525     if (TheRenderer == nullptr) {
526         return 9;
527     }
528 
529     gRenderer = TheRenderer;    // weak pointer for interrupt signal handler
530 
531     if (!opts.quiet) setupTimer(TheRenderer);
532 
533     if (opts.maxShapes > 0)
534         TheRenderer->setMaxShapes(opts.maxShapes);
535 
536     if (opts.animationFrames == 0)
537         TheRenderer->run(nullptr, false);
538 
539     opts.width = TheRenderer->m_width;
540     opts.height = TheRenderer->m_height;
541     opts.crop = opts.crop && !(myDesign->isTiled() || myDesign->isFrieze());
542 
543     switch (opts.format) {
544         case options::BMPfile:
545         case options::PNGfile: {
546             png = std::make_unique<pngCanvas>(
547                                     opts.output.c_str(), opts.quiet, opts.width, opts.height,
548                                     pixfmt, opts.crop, opts.animationFrames, opts.variation,
549                                     opts.format == options::BMPfile, TheRenderer.get(),
550                                     opts.widthMult, opts.heightMult, opts.outputTemp);
551             myCanvas = static_cast<Canvas*>(png.get());
552             if (png->mWidth != opts.width || png->mHeight != opts.height) {
553                 TheRenderer->resetSize(png->mWidth, png->mHeight);
554                 opts.width = TheRenderer->m_width;
555                 opts.height = TheRenderer->m_height;
556             }
557             break;
558         }
559         case options::SVGfile: {
560             string name = makeCFfilename(opts.output.c_str(), 0, 0, opts.variation);
561             svg = std::make_unique<SVGCanvas>(name.c_str(), opts.width, opts.height, opts.crop,
562                                               nullptr, -1, opts.outputTemp);
563             myCanvas = static_cast<Canvas*>(svg.get());
564             if (svg->mError)
565                 cerr << "Failed to open SVG file." << endl;
566             break;
567         }
568         case options::MOVfile: {
569             string name = makeCFfilename(opts.output.c_str(), 0, 0, opts.variation);
570             mov = std::make_unique<ffCanvas>(name.c_str(), pixfmt, opts.width, opts.height,
571                                              opts.animationFPS, opts.animationCodec,
572                                              opts.outputTemp);
573             if (mov->mErrorMsg) {
574                 cerr << "Failed to create movie file: " << mov->mErrorMsg << endl;
575                 exit(8);
576             }
577             myCanvas = static_cast<Canvas*>(mov.get());
578             break;
579         }
580         case options::JSONfile:
581             break;
582     }
583 
584     if (myCanvas->mError || system.error(false) || TheRenderer->requestStop) {
585         cleanupTimer();
586         Renderer::AbortEverything = true;
587         return 5;
588     }
589 
590     if (opts.outputTime) {
591         clock_t toTime = clock();
592         clock_t runTime = (toTime - fromTime) / clocksPerMsec;
593         *myCout << "The cfdg file took " << prettyInt(runTime) << " msec to execute." << endl;
594         fromTime = toTime;
595     }
596 
597     if (opts.animationFrames) {
598         TheRenderer->animate(myCanvas, opts.animationFrames, opts.animateFrame, opts.animationZoom);
599     } else {
600         TheRenderer->draw(myCanvas);
601     }
602     *myCout << endl;
603 
604     if (!opts.quiet) cleanupTimer();
605 
606     *myCout << "DONE!" << endl;
607     *myCout << "The output file name is " <<
608     makeCFfilename(opts.output.c_str(), 0, 0, opts.variation) << endl;
609 
610     if (opts.outputTime) {
611         clock_t toTime = clock();
612         clock_t runTime = (toTime - fromTime) / clocksPerMsec;
613         *myCout << "The cfdg file took " << prettyInt(runTime) << " msec to render." << endl;
614         runTime = (toTime - startTime) / clocksPerMsec;
615         *myCout << "The cfdg file took a total of " << prettyInt(runTime) << " msec to process." << endl;
616     }
617 
618         Renderer::AbortEverything = !(opts.paramTest);
619         actualFileName = myCanvas->mFileName;
620     }   // delete canvas & renderer
621 
622     if (!opts.displayExec.empty()) {
623         opts.displayExec += ' ';
624         opts.displayExec += actualFileName;
625         if (std::system(opts.displayExec.c_str()) != 0)
626             cerr << "\n\nError viewing the output file.\n" << endl;
627     }
628 
629     if (opts.paramTest) {
630         myDesign.reset();   // Delete the AST and its parameters before checking
631         if (Renderer::ParamCount) {
632             cerr << "Left-over parameter blocks in memory:" << prettyInt(static_cast<unsigned long>(Renderer::ParamCount)) << endl;
633 			return 88;
634 		} else {
635             *myCout << "All parameter blocks deleted" << endl;
636 		}
637     }
638 
639     return 0;
640 }
641 
642 
643