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