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