1 // cfdgimpl.cpp
2 // this file is part of Context Free
3 // ---------------------
4 // Copyright (C) 2006-2008 Mark Lentczner - markl@glyphic.com
5 // Copyright (C) 2006-2013 John Horigan - john@glyphic.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 //
21 // John Horigan can be contacted at john@glyphic.com or at
22 // John Horigan, 1209 Villa St., Mountain View, CA 94041-1123, USA
23 //
24 // Mark Lentczner can be contacted at markl@glyphic.com or at
25 // Mark Lentczner, 1209 Villa St., Mountain View, CA 94041-1123, USA
26 //
27 //
28 
29 
30 #define _USE_MATH_DEFINES 1
31 #include "cfdgimpl.h"
32 #include "builder.h"
33 #include "renderimpl.h"
34 #include "primShape.h"
35 #include <cassert>
36 #include <algorithm>
37 #include "astreplacement.h"
38 #include <limits>
39 #include <cstring>
40 #include <cmath>
41 #include <iomanip>
42 #include "agg_trans_affine_time.h"
43 #include <cstddef>
44 
45 #include "json3.hpp"
46 using json = nlohmann::json;
47 
48 #ifdef _WIN32
49 #pragma warning( disable : 4800 4189 )
50 #endif
51 
52 #include <cmath>
53 
54 using namespace AST;
55 
56 
CFDGImpl(AbstractSystem * m)57 CFDGImpl::CFDGImpl(AbstractSystem* m)
58 : mPostDtorCleanup(m), m_backgroundColor(1, 1, 1, 1), mStackSize(0),
59   mInitShape(nullptr), m_system(m), m_builder(nullptr), m_impure(false),
60   m_Parameters(0), ParamDepth({NoParameter}),
61   mTileOffset(0, 0), needle(0, CfdgError::Default)
62 {
63     // Initialize the shape table with the primitive shapes so that they get the
64     // shape number that matches their primitive shape number.
65     for (auto&& name: primShape::shapeNames) {
66         int num = encodeShapeName(name, CfdgError::Default);
67         assert(num >= 0 && num < primShape::numTypes && primShape::shapeNames[num] == name);
68         _unused(num);
69     }
70 
71     initVariables();
72 
73     mCFDGcontents.isGlobal = true;
74 #ifdef EXTREME_PARAM_DEBUG
75     StackRule::ParamMap.clear();
76     StackRule::ParamUID = 0;
77 #endif
78 }
79 
80 void
initVariables()81 CFDGImpl::initVariables()
82 {
83     std::string pi_name("\xcf\x80");           // UTF8-encoded pi symbol
84     int pi_num = encodeShapeName(pi_name, CfdgError::Default);
85     def_ptr pi = std::make_unique<ASTdefine>(pi_name, CfdgError::Default);
86     pi->mExpression = std::make_unique<ASTreal>(M_PI, CfdgError::Default);
87     pi->mShapeSpec.shapeType = pi_num;
88     mCFDGcontents.addDefParameter(pi_num, pi.get(), CfdgError::Default, CfdgError::Default);
89     mCFDGcontents.mBody.push_back(std::move(pi));
90 
91     std::initializer_list<std::string> circleNames = {
92         "\xe2\x9a\xab", "\xe2\x97\x8f", "\xe2\xac\xa4"
93     };
94     for (auto name: circleNames) {  // no auto&&, we want to make a copy
95         int num = encodeShapeName(name, CfdgError::Default);
96         def_ptr def = std::make_unique<ASTdefine>(name, CfdgError::Default);
97         def->mExpression = std::make_unique<ASTruleSpecifier>(primShape::circleType, "CIRCLE", nullptr, CfdgError::Default, nullptr);
98         def->mShapeSpec.shapeType = num;
99         mCFDGcontents.addDefParameter(num, def.get(), CfdgError::Default, CfdgError::Default);
100         mCFDGcontents.mBody.push_back(std::move(def));
101     }
102 
103     std::initializer_list<std::string> squareNames = {
104         "\xe2\xac\x9b", "\xe2\x97\xbc", "\xe2\x97\xbe", "\xef\xbf\xad", "\xe2\x96\xa0"
105     };
106     for (auto name: squareNames) {
107         int num = encodeShapeName(name, CfdgError::Default);
108         def_ptr def = std::make_unique<ASTdefine>(name, CfdgError::Default);
109         def->mExpression = std::make_unique<ASTruleSpecifier>(primShape::squareType, "SQUARE", nullptr, CfdgError::Default, nullptr);
110         def->mShapeSpec.shapeType = num;
111         mCFDGcontents.addDefParameter(num, def.get(), CfdgError::Default, CfdgError::Default);
112         mCFDGcontents.mBody.push_back(std::move(def));
113     }
114 
115     std::initializer_list<std::string> triangleNames = {
116         "\xe2\x96\xb2", "\xe2\x96\xb4"
117     };
118     for (auto name: triangleNames) {
119         int num = encodeShapeName(name, CfdgError::Default);
120         def_ptr def = std::make_unique<ASTdefine>(name, CfdgError::Default);
121         def->mExpression = std::make_unique<ASTruleSpecifier>(primShape::triangleType, "TRIANGLE", nullptr, CfdgError::Default, nullptr);
122         def->mShapeSpec.shapeType = num;
123         mCFDGcontents.addDefParameter(num, def.get(), CfdgError::Default, CfdgError::Default);
124         mCFDGcontents.mBody.push_back(std::move(def));
125     }
126 }
127 
~CFDGImpl()128 CFDGImpl::~CFDGImpl()
129 {
130 }
131 
132 Shape
getInitialShape(RendererAST * r)133 CFDGImpl::getInitialShape(RendererAST* r)
134 {
135     Shape init;
136     init.mWorldState.m_Color = HSBColor(0.0, 0.0, 0.0, 1.0);
137     init.mWorldState.m_ColorTarget = HSBColor(0.0, 0.0, 0.0, 1.0);
138     init.mWorldState.m_time.tend = 1.0;
139     mInitShape->replace(init, r);
140     init.mWorldState.m_transform.tx += mTileOffset.x;
141     init.mWorldState.m_transform.ty += mTileOffset.y;
142     return init;
143 }
144 
145 const agg::rgba&
getBackgroundColor()146 CFDGImpl::getBackgroundColor()
147 {
148     return m_backgroundColor;
149 }
150 
151 void
setBackgroundColor(RendererAST * r)152 CFDGImpl::setBackgroundColor(RendererAST* r)
153 {
154     Modification white;
155     white.m_Color = HSBColor(0.0, 0.0, 1.0, 1.0);
156     if (hasParameter(CFG::Background, white, r)) {
157         white.m_Color.getRGBA(m_backgroundColor);
158         if (!usesAlpha)
159             m_backgroundColor.a = 1.0;
160     }
161 }
162 
163 const ASTrule*
findRule(int shapetype,double r)164 CFDGImpl::findRule(int shapetype, double r)
165 {
166     needle.mNameIndex = shapetype;
167     needle.mWeight = r;
168 
169     auto first = lower_bound(mRules.cbegin(), mRules.cend(),
170                              &needle, ASTrule::compareLT);
171     if (first == mRules.cend() || (*first)->mNameIndex != shapetype)
172         throw CfdgError("Cannot find a rule for a shape (very helpful I know).");
173     return *first;
174 }
175 
176 // Search for a rule in the mRules list even before it is sorted
177 const ASTrule*
findRule(int shapetype)178 CFDGImpl::findRule(int shapetype)
179 {
180     auto rule = std::find_if(mRules.cbegin(), mRules.cend(),
181                              [=](ASTrule* r){return r->mNameIndex == shapetype;});
182     return rule == mRules.cend() ? nullptr : *rule;
183 }
184 
185 // Adds a new rule/path to the rule container. Updates information about the rule
186 // in the m_shapeTypes[] vector. If the rule is known to have parameters then
187 // they are copied into the new rule.
188 bool
addRule(ASTrule * r)189 CFDGImpl::addRule(ASTrule* r)
190 {
191     mRules.push_back(r);
192 
193     ShapeType& shapeItem = m_shapeTypes[r->mNameIndex];
194     if (shapeItem.shapeType == newShape)
195         shapeItem.shapeType = (r->isPath) ? pathType : ruleType;
196     if (shapeItem.parameters && !shapeItem.parameters->empty())
197         r->mRuleBody.mParameters = *(shapeItem.parameters);
198     shapeItem.hasRules = true;
199     return shapeItem.isShape;
200 }
201 
202 void
addParameter(Parameter p)203 CFDGImpl::addParameter(Parameter p)
204 {
205     m_Parameters |= p;
206     usesColor = m_Parameters & Color;
207     usesTime = m_Parameters & Time;
208     usesFrameTime = m_Parameters & FrameTime;
209     usesBlendMode = m_Parameters & Blend;
210 }
211 
212 RGBA8
getColor(const HSBColor & hsb)213 CFDGImpl::getColor(const HSBColor& hsb)
214 {
215     agg::rgba c;
216     hsb.getRGBA(c);
217     if (uses16bitColor) {
218         return RGBA8(c);
219     } else {
220         agg::rgba8 c8 = agg::rgba8(c);
221         return RGBA8(c8);
222     }
223 }
224 
225 bool
isTiled(agg::trans_affine * tr,double * x,double * y) const226 CFDGImpl::isTiled(agg::trans_affine* tr, double* x, double* y) const
227 {
228     yy::location loc;
229     if (!hasParameter(CFG::Tile, AST::ModType, loc)) return false;
230     if (mTileMod.m_transform.sx == 0.0 || mTileMod.m_transform.sy == 0.0) return false;
231     if (tr) {
232         *tr = mTileMod.m_transform;
233         tr->tx = tr->ty = 0.0;
234     }
235     if (x && y) {
236         double o_x = 0.0;
237         double o_y = 0.0;
238         double u_x = 1.0;
239         double u_y = 0.0;
240         double v_x = 0.0;
241         double v_y = 1.0;
242 
243         mTileMod.m_transform.transform(&o_x, &o_y);
244         mTileMod.m_transform.transform(&u_x, &u_y);
245         mTileMod.m_transform.transform(&v_x, &v_y);
246 
247         if (fabs(u_y - o_y) >= 0.0001 && fabs(v_x - o_x) >= 0.0001)
248             CfdgError::Error(loc, "Tile must be aligned with the X or Y axis.", m_builder);
249         if ((u_x - o_x) < 0.0 || (v_y - o_y) < 0.0)
250             CfdgError::Error(loc, "Tile must be in the positive X/Y quadrant.", m_builder);
251 
252         *x = u_x - o_x;
253         *y = v_y - o_y;
254     }
255     return true;
256 }
257 
258 CFDG::frieze_t
isFrieze(agg::trans_affine * tr,double * x,double * y) const259 CFDGImpl::isFrieze(agg::trans_affine* tr, double* x, double* y) const
260 {
261     yy::location loc;
262     if (!hasParameter(CFG::Tile, AST::ModType, loc)) return no_frieze;
263     if (mTileMod.m_transform.sx != 0.0 && mTileMod.m_transform.sy != 0.0) return no_frieze;
264     if (mTileMod.m_transform.sx == 0.0 && mTileMod.m_transform.sy == 0.0) return no_frieze;
265     if (tr) {
266         *tr = mTileMod.m_transform;
267         tr->tx = tr->ty = 0.0;
268     }
269     if (x && y) {
270         double o_x = 0.0;
271         double o_y = 0.0;
272         double u_x = 1.0;
273         double u_y = 0.0;
274         double v_x = 0.0;
275         double v_y = 1.0;
276 
277         mTileMod.m_transform.transform(&o_x, &o_y);
278         mTileMod.m_transform.transform(&u_x, &u_y);
279         mTileMod.m_transform.transform(&v_x, &v_y);
280 
281         if (fabs(u_y - o_y) >= 0.0001 || fabs(v_x - o_x) >= 0.0001)
282             CfdgError::Error(loc, "Frieze must be aligned with the X and Y axis.", m_builder);
283         if ((u_x - o_x) < 0.0 || (v_y - o_y) < 0.0)
284             CfdgError::Error(loc, "Frieze must be in the positive X/Y quadrant.", m_builder);
285 
286         *x = u_x - o_x;
287         *y = v_y - o_y;
288     }
289     return mTileMod.m_transform.sx == 0.0 ? frieze_y : frieze_x;
290 }
291 
292 bool
isSized(double * x,double * y) const293 CFDGImpl::isSized(double* x, double* y) const
294 {
295     yy::location loc;
296     if (!hasParameter(CFG::Size, AST::ModType, loc)) return false;
297     if (x) *x = mSizeMod.m_transform.sx;
298     if (y) *y = mSizeMod.m_transform.sy;
299     if (mSizeMod.m_transform.shx != 0.0 || mSizeMod.m_transform.shy != 0.0)
300         CfdgError::Error(loc, "Size specification must not be rotated or skewed.", m_builder);
301     return true;
302 }
303 
304 bool
isTimed(agg::trans_affine_time * t) const305 CFDGImpl::isTimed(agg::trans_affine_time* t) const
306 {
307     yy::location loc;
308     if (!hasParameter(CFG::Time, AST::ModType, loc)) return false;
309     if (t) *t = mTimeMod.m_time;
310     if (mTimeMod.m_time.tbegin >= mTimeMod.m_time.tend)
311         CfdgError::Error(loc, "Time specification must have positive duration.", m_builder);
312     return true;
313 }
314 
315 void
getSymmetry(SymmList & syms,RendererAST * r)316 CFDGImpl::getSymmetry(SymmList& syms, RendererAST* r)
317 {
318     syms.clear();
319     const ASTexpression* e = hasParameter(CFG::Symmetry);
320     std::vector<const ASTmodification*> left = getTransforms(e, syms, r, isTiled(), mTileMod.m_transform);
321 
322     if (!left.empty()) {
323         CfdgError::Error(left.front()->where, "At least one term was invalid", m_builder);
324     }
325 }
326 
327 void
serialize(std::ostream & out)328 CFDGImpl::serialize(std::ostream& out)
329 {
330     json j{};
331     json jc{};
332     for (int cfg = 0; cfg < static_cast<int>(CFG::_NumberOf); ++cfg)
333         if (ParamDepth[cfg] != NoParameter)
334             jc[getCfgName(cfg)] = *ParamExp[cfg];
335     j["configuration"] = jc;
336     auto it = mCFDGcontents.mBody.begin();
337     mCFDGcontents.mBody.erase(it, it + 11);     // delete the pre-defines
338     j["cfdg"] = mCFDGcontents;
339     out << std::setw(4) << j << std::endl;
340 }
341 
342 bool
hasParameter(CFG name,double & value,RendererAST * r) const343 CFDGImpl::hasParameter(CFG name, double& value, RendererAST* r) const
344 {
345     const ASTexpression* exp = hasParameter(name);
346     if (!exp || exp->mType != AST::NumericType)
347         return false;
348 
349     if (!exp->isConstant && !r) {
350         CfdgError::Error(exp->where, "This expression must be constant", m_builder);
351         return false;
352     } else {
353         exp->evaluate(&value, 1, r);
354     }
355     return true;
356 }
357 
358 bool
hasParameter(CFG name,Modification & value,RendererAST * r) const359 CFDGImpl::hasParameter(CFG name, Modification& value, RendererAST* r) const
360 {
361     const ASTexpression* exp = hasParameter(name);
362     if (!exp || exp->mType != AST::ModType)
363         return false;
364 
365     if (!exp->isConstant && !r) {
366         CfdgError::Error(exp->where, "This expression must be constant", m_builder);
367         return false;
368     } else {
369         exp->evaluate(value, true, r);
370     }
371     return true;
372 }
373 
374 bool
hasParameter(CFG name,AST::expType t,yy::location & where) const375 CFDGImpl::hasParameter(CFG name, AST::expType t, yy::location& where) const
376 {
377     const ASTexpression* exp = hasParameter(name);
378     if (!exp || exp->mType != t)
379         return false;
380 
381     where = exp->where;
382     return true;
383 }
384 
385 const ASTexpression*
hasParameter(CFG name) const386 CFDGImpl::hasParameter(CFG name) const
387 {
388     assert(static_cast<std::size_t>(name) < static_cast<std::size_t>(CFG::_NumberOf));
389 
390     if (ParamDepth[name] == NoParameter)
391         return nullptr;
392 
393     return ParamExp[name].get();
394 }
395 
396 void
addParameter(CFG var,exp_ptr e,int depth)397 CFDGImpl::addParameter(CFG var, exp_ptr e, int depth)
398 {
399     if (depth < ParamDepth[var]) {
400         ParamDepth[var] = depth;
401         ParamExp[var] = std::move(e);
402     }
403 }
404 
405 void
rulesLoaded()406 CFDGImpl::rulesLoaded()
407 {
408     // thanks to by Brent Yorgey
409     std::vector<double> weightsums( m_shapeTypes.size(), 0.0 );
410     std::vector<double> percentweightsums( m_shapeTypes.size(), 0.0 );
411     std::vector<double> unitweightsums( m_shapeTypes.size(), 0.0 );
412     std::vector<int> rulecounts( m_shapeTypes.size(), 0 );
413     std::vector<int> weightTypes( m_shapeTypes.size(), 0 );
414 
415     // first pass: sum all the weights for each shape type
416     for (auto&& r: mRules) {
417         if (r->weightType == ASTrule::PercentWeight) {
418             percentweightsums[ r->mNameIndex ] += r->mWeight;
419             if (percentweightsums[ r->mNameIndex ] > 1.0001)
420                 CfdgError::Error(r->mLocation, "Percentages exceed 100%", m_builder);
421         } else {
422             weightsums[ r->mNameIndex ] += r->mWeight;
423         }
424         rulecounts[ r->mNameIndex ]++;
425         weightTypes[r->mNameIndex ] |= static_cast<int>(r->weightType);
426     }
427 
428     // second pass: normalize each weight by dividing by the
429     // total weight for that shape type
430     for (auto&& r: mRules) {
431         double weight = r->mWeight / weightsums[ r->mNameIndex ];   // may be infinity or NaN
432         if (weightTypes[r->mNameIndex ] & static_cast<int>(ASTrule::PercentWeight)) {
433             if (r->weightType == ASTrule::PercentWeight)
434                 weight = r->mWeight;
435             else {
436                 weight *= 1.0 - percentweightsums[ r->mNameIndex ];
437                 if (percentweightsums[ r->mNameIndex ] > 0.9999)
438                     CfdgError::Warning(r->mLocation, "Percentages sum to 100%, this rule has no weight");
439             }
440         }
441         if (weightTypes[r->mNameIndex] == static_cast<int>(ASTrule::PercentWeight) &&
442             fabs(percentweightsums[ r->mNameIndex ] - 1.0) > 0.0001)
443         {
444             CfdgError::Warning(r->mLocation, "Percentages do not sum to 100%");
445         }
446         if (!std::isfinite(weight)) weight = 0.0;
447         unitweightsums[ r->mNameIndex ] += weight;
448         if (--rulecounts[ r->mNameIndex ]) {
449             r->mWeight = unitweightsums[ r->mNameIndex ];
450         } else {
451             // make sure that last rule of a type has a weightsum >= 1.0
452             r->mWeight = 1.0;
453         }
454     }
455 
456     // third pass: sort the rules by shape type, preserving the rule order
457     // with respect to rules of the same shape type
458     sort(mRules.begin(), mRules.end(), ASTrule::compareLT);
459 
460     try {
461         m_builder->mLocalStackDepth = 0;
462         m_builder->mInPathContainer = false;
463         mCFDGcontents.compile(CompilePhase::TypeCheck, m_builder);
464         m_builder->mInPathContainer = false;
465         if (!m_builder->mErrorOccured)
466             mCFDGcontents.compile(CompilePhase::Simplify, m_builder);
467     } catch (DeferUntilRuntime&) {
468         CfdgError::Error(CfdgError::Default, "Unexpected exception during compile.");
469     }
470 
471     // Wait until done and then update these members
472     double value;
473     uses16bitColor = hasParameter(CFG::ColorDepth, value, nullptr) &&
474         std::floor(value) == 16.0;
475 
476     if (hasParameter(CFG::Color, value, nullptr))
477         usesColor = value != 0.0;
478 
479     if (hasParameter(CFG::Alpha, value, nullptr))
480         usesAlpha = value != 0.0;
481 
482     if (const ASTexpression* e = hasParameter(CFG::Background))
483         if (const ASTmodification* m = dynamic_cast<const ASTmodification*>(e)) {
484             usesAlpha = m->modData.m_Color.a != 1.0;
485             for (auto& term: m->modExp)
486                 if (term->modType == ASTmodTerm::alpha || term->modType == ASTmodTerm::alphaTarg)
487                     usesAlpha = true;
488         }
489 }
490 
491 int
numRules()492 CFDGImpl::numRules()
493 {
494     return static_cast<int>(mRules.size());
495 }
496 
497 std::string
decodeShapeName(int shapetype)498 CFDGImpl::decodeShapeName(int shapetype)
499 {
500     if (shapetype < int(m_shapeTypes.size()))
501         return m_shapeTypes[shapetype].name;
502     else
503         return ("**unnamed shape**");
504 }
505 
506 const yy::location&
decodeShapeLocation(int shapetype)507 CFDGImpl::decodeShapeLocation(int shapetype)
508 {
509     if (shapetype < int(m_shapeTypes.size()))
510         return m_shapeTypes[shapetype].firstUse;
511     else
512         return CfdgError::Default;
513 }
514 
515 int
tryEncodeShapeName(const std::string & s) const516 CFDGImpl::tryEncodeShapeName(const std::string& s) const
517 {
518     std::wstring c = m_system->normalize(s);
519     return tryEncodeShapeName(c);
520 }
521 
522 int
tryEncodeShapeName(const std::wstring & s) const523 CFDGImpl::tryEncodeShapeName(const std::wstring& s) const
524 {
525     for (unsigned int i = 0; i < m_shapeTypes.size(); i++) {
526         if (s == m_shapeTypes[i].canonicalName) {
527             return i;
528         }
529     }
530 
531     return -1;
532 }
533 
534 int
encodeShapeName(const std::string & s,const yy::location & where)535 CFDGImpl::encodeShapeName(const std::string& s, const yy::location& where)
536 {
537     std::wstring c = m_system->normalize(s);
538     int i = tryEncodeShapeName(c);
539     if (i >= 0) return i;
540 
541     m_shapeTypes.emplace_back(s, std::move(c), where);
542     return static_cast<int>(m_shapeTypes.size()) - 1;
543 }
544 
545 int
getShapeType(int shapetype)546 CFDGImpl::getShapeType(int shapetype)
547 {
548     return m_shapeTypes[shapetype].shapeType;
549 }
550 
551 bool
shapeHasRules(int shapetype)552 CFDGImpl::shapeHasRules(int shapetype)
553 {
554     if (shapetype < int(m_shapeTypes.size()))
555         return m_shapeTypes[shapetype].hasRules;
556     else
557         return false;
558 }
559 
560 void
setShapeHasNoParams(int shapetype,const ASTexpression * args)561 CFDGImpl::setShapeHasNoParams(int shapetype, const ASTexpression* args)
562 {
563     if (shapetype < int(m_shapeTypes.size()) && args == nullptr)
564         m_shapeTypes[shapetype].shouldHaveNoParams = true;
565 }
566 
567 bool
getShapeHasNoParams(int shapetype)568 CFDGImpl::getShapeHasNoParams(int shapetype)
569 {
570     if (shapetype < int(m_shapeTypes.size()))
571         return m_shapeTypes[shapetype].shouldHaveNoParams;
572     return false;
573 }
574 
575 const char*
setShapeParams(int shapetype,AST::ASTrepContainer & p,int argSize,bool isPath)576 CFDGImpl::setShapeParams(int shapetype, AST::ASTrepContainer& p, int argSize, bool isPath)
577 {
578     ShapeType& shape = m_shapeTypes[shapetype];
579     if (shape.isShape) {
580         // There has been a forward declaration, so this shape declaration
581         // just introduces the shape elements
582         if (!(p.mParameters.empty()))
583             return  "Shape has already been declared. "
584                     "Parameter declaration must be on the first shape declaration only.";
585         if (shape.shapeType == pathType && !isPath)
586             return "Shape name already in use by another rule or path";
587         if (isPath)
588             return "Path name already in use by another rule or path";
589         return nullptr;
590     }
591     if (shape.shapeType != newShape)
592         return "Shape name already in use by another rule or path";
593 
594     shape.parameters = std::make_unique<AST::ASTparameters>(p.mParameters);
595     shape.isShape = true;
596     shape.argSize = argSize;
597     shape.shapeType = isPath ? pathType : newShape;
598     return nullptr;
599 }
600 
601 const AST::ASTparameters*
getShapeParams(int shapetype) const602 CFDGImpl::getShapeParams(int shapetype) const
603 {
604     if (shapetype < 0 || shapetype >= int(m_shapeTypes.size()) ||
605         !m_shapeTypes[shapetype].isShape)
606         return nullptr;
607     return m_shapeTypes[shapetype].parameters.get();
608 }
609 
610 int
getShapeParamSize(int shapetype)611 CFDGImpl::getShapeParamSize(int shapetype)
612 {
613     if (shapetype < 0 || shapetype >= int(m_shapeTypes.size()))
614         return 0;
615     return m_shapeTypes[shapetype].argSize;
616 }
617 
618 int
reportStackDepth(int size)619 CFDGImpl::reportStackDepth(int size)
620 {
621     if (size > mStackSize)
622         mStackSize = size;
623     return mStackSize;
624 }
625 
626 void
resetCachedPaths()627 CFDGImpl::resetCachedPaths()
628 {
629     for (ASTrule* rule: mRules)
630         rule->mCachedPath.reset();
631 }
632 
633 AST::ASTdefine*
declareFunction(int nameIndex,AST::ASTdefine * def)634 CFDGImpl::declareFunction(int nameIndex, AST::ASTdefine* def)
635 {
636     AST::ASTdefine* prev = findFunction(nameIndex);
637     if (prev)
638         return prev;
639 
640     mFunctions[nameIndex] = def;
641     return def;
642 }
643 
644 AST::ASTdefine*
findFunction(int nameIndex)645 CFDGImpl::findFunction(int nameIndex)
646 {
647     auto fi = mFunctions.find(nameIndex);
648     if (fi != mFunctions.end())
649         return fi->second;
650     return nullptr;
651 }
652 
653 renderer_ptr
renderer(const cfdg_ptr & ptr,int width,int height,double minSize,int variation,double border)654 CFDGImpl::renderer(const cfdg_ptr& ptr, int width, int height, double minSize,
655                     int variation, double border)
656 {
657     ASTexpression* startExp = ParamExp[CFG::StartShape].get();
658 
659     if (!startExp) {
660         m_system->message("No startshape found");
661         m_system->error();
662         return nullptr;
663     }
664 
665     if (ASTstartSpecifier* startSpec = dynamic_cast<ASTstartSpecifier*>(startExp)) {
666         ParamExp[CFG::StartShape].release();
667         mod_ptr mods = std::move(startSpec->mModification);
668         ruleSpec_ptr start(static_cast<ASTruleSpecifier*>(startSpec));
669         mInitShape = std::make_unique<ASTreplacement>(std::move(start), std::move(mods));
670         mInitShape->mChildChange.addEntropy(mInitShape->mShapeSpec.entropyVal);
671     } else {
672         CfdgError err(startExp->where, "Type error in startshape");
673         m_system->error();
674         m_system->syntaxError(err);
675         return nullptr;
676     }
677 
678     std::unique_ptr<RendererImpl> r;
679     try {
680         r = std::make_unique<RendererImpl>(ptr, width, height, minSize, variation, border);
681         r->mImpure = m_impure;
682         Modification tiled;
683         Modification sized;
684         Modification timed;
685         double       maxShape;
686         if (hasParameter(CFG::Tile, tiled, nullptr)) {
687             mTileMod = tiled;
688             mTileOffset.x = mTileMod.m_transform.tx;
689             mTileOffset.y = mTileMod.m_transform.ty;
690             mTileMod.m_transform.tx = mTileMod.m_transform.ty = 0.0;
691         }
692         if (hasParameter(CFG::Size, sized, nullptr)) {
693             mSizeMod = sized;
694             mTileOffset.x = mSizeMod.m_transform.tx;
695             mTileOffset.y = mSizeMod.m_transform.ty;
696             mSizeMod.m_transform.tx = mSizeMod.m_transform.ty = 0.0;
697         }
698         if (hasParameter(CFG::Time, timed, nullptr)) {
699             if (timed.m_time.tend <= timed.m_time.tbegin ||
700                 !std::isfinite(timed.m_time.tbegin) ||
701                 !std::isfinite(timed.m_time.tend) ||
702                 !std::isfinite(timed.m_time.st))
703             {
704                 yy::location loc;
705                 hasParameter(CFG::Time, AST::ModType, loc);
706                 CfdgError err(loc, "Illegal CF::Time specification");
707                 m_system->error();
708                 m_system->syntaxError(err);
709                 return nullptr;
710             }
711             mTimeMod = timed;
712             double frame_v, ftime_v;
713             bool frame = hasParameter(CFG::Frame, frame_v, nullptr);
714             bool ftime = hasParameter(CFG::FrameTime, ftime_v, nullptr);
715             if (frame || ftime) {
716                 if (frame && ftime) {
717                     m_system->message("It is not necessary to specify both CF::Frame and CF::FrameTime");
718                 } else if (frame) {
719                     ftime_v = (timed.m_time.tend - timed.m_time.tbegin) * frame_v + timed.m_time.tbegin;
720                     exp_ptr e = std::make_unique<AST::ASTreal>(ftime_v, CfdgError::Default);
721                     addParameter(CFG::FrameTime, std::move(e), 0);
722                     m_system->message("Setting CF::FrameTime to %f from CF::Frame", ftime_v);
723                 } else /* if (ftime) */ {
724                     frame_v = (ftime_v - timed.m_time.tbegin) / (timed.m_time.tend - timed.m_time.tbegin);
725                     exp_ptr e = std::make_unique<AST::ASTreal>(frame_v, CfdgError::Default);
726                     addParameter(CFG::Frame, std::move(e), 0);
727                     m_system->message("Setting CF::Frame to %f from CF::FrameTime", frame_v);
728                 }
729             }
730         }
731         if (hasParameter(CFG::MaxShapes, maxShape, r.get())) {
732             if (maxShape > 1)
733                 r->setMaxShapes(static_cast<int>(maxShape));
734         }
735         r->initBounds();
736     } catch (CfdgError& e) {
737         m_system->error();
738         m_system->syntaxError(e);
739         return nullptr;     // deletes this
740     }
741     return r;
742 }
743 
744