1 // astreplacement.cpp
2 // this file is part of Context Free
3 // ---------------------
4 // Copyright (C) 2009-2014 John Horigan - john@glyphic.com
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 //
20 // John Horigan can be contacted at john@glyphic.com or at
21 // John Horigan, 1209 Villa St., Mountain View, CA 94041-1123, USA
22 //
23 //
24 
25 
26 #include "astreplacement.h"
27 
28 #include "agg2/agg_basics.h"
29 #include "pathIterator.h"
30 #include "json3.hpp"
31 #include <cassert>
32 #include <atomic>
33 #include "rendererAST.h"
34 #include "attributes.h"
35 #include "builder.h"
36 #include <typeinfo>
37 #include <cmath>
38 #include <cstddef>
39 
40 using std::floor;
41 
42 namespace AST {
to_json(json & j,const ASTparameter & m)43     void to_json(json& j, const ASTparameter& m)
44     {
45         try {
46             std::string typeName = ASTparameter::typeNames.at(m.mType);
47             j = json{{"parameter type", typeName}};
48         } catch (std::out_of_range&) {
49             j = json{{"parameter type", "mixed"}};
50         }
51         if (m.mType == NumericType)
52             j["parameter tuple size"] = m.mTuplesize;
53         j["natural"] = m.isNatural;
54         j["parameter name"] = CFDG::ShapeToString(m.mName);
55     }
56 
to_json(json & j,const ASTreplacement & p)57     void to_json(json& j, const ASTreplacement& p) {
58         p.to_json(j);
59     }
60 
to_json(json & j,const ASTrepContainer & p)61     void to_json(json& j, const ASTrepContainer& p) {
62         j = json::array();
63         for (const auto& elem: p.mBody)
64             j.push_back(*elem);
65     }
66 
67 
68     CommandInfo::UIDtype ASTcompiledPath::GlobalPathUID(1);
69 
70     void
addParameter(const std::string & type,int index,const yy::location & typeLoc,const yy::location & nameLoc)71     ASTrepContainer::addParameter(const std::string& type, int index,
72                                   const yy::location& typeLoc, const yy::location& nameLoc)
73     {
74         mParameters.emplace_back(type, index, typeLoc + nameLoc);
75         ASTparameter& param = mParameters.back();
76         param.isParameter = true;
77         param.checkParam(typeLoc, nameLoc);
78     }
79 
80     ASTparameter&
addDefParameter(int index,ASTdefine * def,const yy::location & nameLoc,const yy::location & expLoc)81     ASTrepContainer::addDefParameter(int index, ASTdefine* def,
82                                      const yy::location& nameLoc,
83                                      const yy::location& expLoc)
84     {
85         mParameters.emplace_back(index, def, nameLoc + expLoc);
86         ASTparameter& b = mParameters.back();
87         b.checkParam(nameLoc, nameLoc);
88         return b;
89     }
90 
91     void
addLoopParameter(int index,const yy::location & nameLoc)92     ASTrepContainer::addLoopParameter(int index, const yy::location& nameLoc)
93     {
94         mParameters.emplace_back(index, nameLoc);
95         mParameters.back().checkParam(nameLoc, nameLoc);
96     }
97 
98     void
compile(CompilePhase ph,Builder * b,ASTloop * loop,ASTdefine * def)99     ASTrepContainer::compile(CompilePhase ph, Builder* b, ASTloop* loop, ASTdefine* def)
100     {
101         // Delete all of the incomplete parameters inserted during parse
102         if (ph == CompilePhase::TypeCheck) {
103             for (std::size_t i = 0; i < mParameters.size(); ++i)
104                 if (!mParameters[i].isParameter  && !mParameters[i].isLoopIndex) {
105                     mParameters.resize(i);
106                     break;
107                 }
108         }
109 
110         b->push_repContainer(*this);
111         if (loop)
112             loop->compileLoopMod(b);
113         for (auto& rep: mBody)
114             rep->compile(ph, b);
115         if (def)
116             def->compile(ph, b);
117         b->pop_repContainer(nullptr);
118     }
119 
120 
ASTreplacement(ruleSpec_ptr shapeSpec,mod_ptr mods,const yy::location & loc,repElemListEnum t)121     ASTreplacement::ASTreplacement(ruleSpec_ptr shapeSpec, mod_ptr mods,
122                                    const yy::location& loc, repElemListEnum t) noexcept
123     : mShapeSpec(std::move(shapeSpec)), mRepType(t), mPathOp(unknownPathop),
124       mChildChange(std::move(mods), loc), mLocation(loc)
125     {
126     }
127 
ASTreplacement(mod_ptr mods,const yy::location & loc,repElemListEnum t)128     ASTreplacement::ASTreplacement(mod_ptr mods, const yy::location& loc,
129                                    repElemListEnum t)
130     : mShapeSpec(), mRepType(t), mPathOp(unknownPathop),
131       mChildChange(std::move(mods), loc), mLocation(loc)
132     {
133     }
134 
ASTreplacement(const std::string & s,const yy::location & loc)135     ASTreplacement::ASTreplacement(const std::string& s, const yy::location& loc)
136     : mShapeSpec(), mRepType(op), mPathOp(unknownPathop),
137       mChildChange(loc), mLocation(loc)
138     {
139         static const std::map<std::string, pathOpEnum> PathOpNames = {
140             { "MOVETO",     MOVETO },
141             { "MOVEREL",    MOVEREL },
142             { "LINETO",     LINETO },
143             { "LINEREL",    LINEREL },
144             { "ARCTO",      ARCTO },
145             { "ARCREL",     ARCREL },
146             { "CURVETO",    CURVETO },
147             { "CURVEREL",   CURVEREL },
148             { "CLOSEPOLY",  CLOSEPOLY }
149         };
150 
151         auto opname = PathOpNames.find(s);
152         assert(opname != PathOpNames.end());
153         mPathOp = opname->second;
154     }
155 
ASTloop(int nameIndex,const std::string & name,const yy::location & nameLoc,exp_ptr args,const yy::location & argsLoc,mod_ptr mods)156     ASTloop::ASTloop(int nameIndex, const std::string& name, const yy::location& nameLoc,
157                      exp_ptr args, const yy::location& argsLoc,
158                      mod_ptr mods)
159     : ASTreplacement(std::move(mods), nameLoc + argsLoc, empty), mLoopArgs(std::move(args)),
160       mLoopModHolder(nullptr), mLoopIndexName(nameIndex), mLoopName(name)
161     {
162         mLoopBody.addLoopParameter(mLoopIndexName, mLocation);
163         mFinallyBody.addLoopParameter(mLoopIndexName, mLocation);
164     }
165 
ASTtransform(const yy::location & loc,exp_ptr mods)166     ASTtransform::ASTtransform(const yy::location& loc, exp_ptr mods)
167     : ASTreplacement(nullptr, loc, empty), mExpHolder(std::move(mods)), mClone(false)
168     {
169     }
170 
ASTdefine(std::string & name,const yy::location & loc)171     ASTdefine::ASTdefine(std::string& name, const yy::location& loc)
172     : ASTreplacement(nullptr, loc, empty), mDefineType(StackDefine),
173       mType(NoType), isNatural(false), mParamSize(0), mConfigDepth(-1)
174     {
175         mName.swap(name);
176         // Set the Modification entropy to parameter name, not its own contents
177         int i = 0;
178         mChildChange.modData.mRand64Seed.seed();
179         mChildChange.modData.mRand64Seed.xorString(mName.c_str(), i);
180     }
181 
182     void
setupLoop(double & start,double & end,double & step,const ASTexpression * e,RendererAST * rti)183     ASTloop::setupLoop(double& start, double& end, double& step, const ASTexpression* e,
184                        RendererAST* rti)
185     {
186         double data[3];
187         switch (e->evaluate(data, 3, rti)) {
188             case 1:
189                 data[1] = data[0];
190                 data[0] = 0.0;
191 		FALLTHROUGH;
192             case 2:
193                 data[2] = 1.0;
194 		FALLTHROUGH;
195             case 3:
196                 break;
197             default:
198                 return;
199         }
200         start = data[0];
201         end = data[1];
202         step = data[2];
203     }
204 
ASTif(exp_ptr ifCond,const yy::location & condLoc)205     ASTif::ASTif(exp_ptr ifCond, const yy::location& condLoc)
206     : ASTreplacement(nullptr, condLoc, empty),
207       mCondition(std::move(ifCond))
208     {
209     }
210 
ASTswitch(exp_ptr switchExp,const yy::location & expLoc)211     ASTswitch::ASTswitch(exp_ptr switchExp, const yy::location& expLoc)
212     : ASTreplacement(nullptr, expLoc, empty),
213       mSwitchExp(std::move(switchExp))
214     {
215     }
216 
217     void
unify()218     ASTswitch::unify()
219     {
220         if (mElseBody.mPathOp != mPathOp) mPathOp = unknownPathop;
221         for (auto&& _case: mCases)
222             if (_case.second->mPathOp != mPathOp)
223                 mPathOp = unknownPathop;
224     }
225 
ASTrule(int i)226     ASTrule::ASTrule(int i)
227     : ASTreplacement(nullptr, CfdgError::Default, rule), mCachedPath(nullptr),
228       mWeight(1.0), isPath(true), mNameIndex(i), weightType(NoWeight)
229     {
230         if (primShape::shapeMap[i].total_vertices() > 0) {
231             static const std::string  move_op("MOVETO");
232             static const std::string  line_op("LINETO");
233             static const std::string   arc_op("ARCTO");
234             static const std::string close_op("CLOSEPOLY");
235             if (i != primShape::circleType) {
236                 primIter shape(&primShape::shapeMap[i]);
237                 double x = 0, y = 0;
238                 unsigned cmd;
239                 while (!agg::is_stop(cmd = shape.vertex(&x, &y))) {
240                     if (agg::is_vertex(cmd)) {
241                         exp_ptr a = std::make_unique<ASTcons>(exp_list({
242                             new ASTreal(x, CfdgError::Default),
243                             new ASTreal(y, CfdgError::Default)
244                         }));
245                         rep_ptr op = std::make_unique<ASTpathOp>(agg::is_move_to(cmd) ? move_op : line_op,
246                             std::move(a), CfdgError::Default);
247                         mRuleBody.mBody.emplace_back(std::move(op));
248                     }
249                 }
250             } else {
251                 exp_ptr a = std::make_unique<ASTcons>(exp_list({
252                     new ASTreal(0.5, CfdgError::Default),
253                     new ASTreal(0.0, CfdgError::Default)
254                 }));
255                 rep_ptr op = std::make_unique<ASTpathOp>(move_op, std::move(a), CfdgError::Default);
256                 mRuleBody.mBody.emplace_back(std::move(op));
257                 a = std::make_unique<ASTcons>(exp_list({
258                     new ASTreal(-0.5, CfdgError::Default),
259                     new ASTreal( 0.0, CfdgError::Default),
260                     new ASTreal( 0.5, CfdgError::Default)
261                 }));
262                 op = std::make_unique<ASTpathOp>(arc_op, std::move(a), CfdgError::Default);
263                 mRuleBody.mBody.emplace_back(std::move(op));
264                 a = std::make_unique<ASTcons>(exp_list({
265                     new ASTreal( 0.5, CfdgError::Default),
266                     new ASTreal( 0.0, CfdgError::Default),
267                     new ASTreal( 0.5, CfdgError::Default)
268                 }));
269                 op = std::make_unique<ASTpathOp>(arc_op, std::move(a), CfdgError::Default);
270                 mRuleBody.mBody.emplace_back(std::move(op));
271             }
272             rep_ptr op = std::make_unique<ASTpathOp>(close_op, exp_ptr(),
273                                                      CfdgError::Default);
274             mRuleBody.mBody.emplace_back(std::move(op));
275             mRuleBody.mRepType = ASTreplacement::op;
276             mRuleBody.mPathOp = AST::MOVETO;
277         }
278     }
279 
~ASTrepContainer()280     ASTrepContainer::~ASTrepContainer()
281     {
282     }
283 
ASTcompiledPath()284     ASTcompiledPath::ASTcompiledPath()
285     : mCached(false), mUseTerminal(false), mParameters(nullptr)
286     {
287         mPathUID = NextPathUID();
288     }
289 
ASTpathOp(const std::string & s,exp_ptr a,const yy::location & loc)290     ASTpathOp::ASTpathOp(const std::string& s, exp_ptr a, const yy::location& loc)
291     : ASTreplacement(s, loc), mArguments(std::move(a)),
292       mOldStyleArguments(nullptr), mArgCount(0), mFlags(0)
293     {
294     }
295 
ASTpathOp(const std::string & s,mod_ptr a,const yy::location & loc)296     ASTpathOp::ASTpathOp(const std::string& s, mod_ptr a, const yy::location& loc)
297     : ASTreplacement(s, loc), mArguments(nullptr),
298       mOldStyleArguments(std::move(a)), mArgCount(0), mFlags(0)
299     {
300     }
301 
ASTpathCommand(const std::string & s,mod_ptr mods,exp_ptr params,const yy::location & loc)302     ASTpathCommand::ASTpathCommand(const std::string& s, mod_ptr mods,
303                                    exp_ptr params, const yy::location& loc)
304     :   ASTreplacement(std::move(mods), loc, command),
305         mMiterLimit(4.0), mStrokeWidth(0.1), mParameters(std::move(params)),
306         mFlags(CF_MITER_JOIN + CF_BUTT_CAP)
307     {
308         if (s == "FILL")
309             mFlags |= CF_FILL;
310         else
311             assert(s == "STROKE");
312     }
313 
314     ASTreplacement::~ASTreplacement() = default;
315 
316     ASTloop::~ASTloop() = default;
317 
318     ASTif::~ASTif() = default;
319 
320     ASTswitch::~ASTswitch() = default;
321 
322     ASTrule::~ASTrule() = default;
323 
324     ASTcompiledPath::~ASTcompiledPath() = default;
325 
326     ASTtransform::~ASTtransform() = default;
327 
328     ASTpathOp::~ASTpathOp() = default;
329 
330     void
replace(Shape & s,RendererAST * r) const331     ASTreplacement::replace(Shape& s, RendererAST* r) const
332     {
333         if (mShapeSpec.argSource == ASTruleSpecifier::NoArgs) {
334             s.mShapeType = mShapeSpec.shapeType;
335             s.mParameters = nullptr;
336         } else {
337             s.mParameters = mShapeSpec.evalArgs(r, s.mParameters.get());
338             if (mShapeSpec.argSource == ASTruleSpecifier::SimpleParentArgs)
339                 s.mShapeType = mShapeSpec.shapeType;
340             else
341                 s.mShapeType = s.mParameters->mRuleName;
342             if (s.mParameters && s.mParameters->mParamCount == 0)
343                 s.mParameters.reset();
344         }
345         r->mCurrentSeed ^= mChildChange.modData.mRand64Seed;
346         r->mCurrentSeed();
347         mChildChange.evaluate(s.mWorldState, true, r);
348         s.mAreaCache = s.mWorldState.area();
349     }
350 
351     void
traverse(const Shape & parent,bool tr,RendererAST * r) const352     ASTreplacement::traverse(const Shape& parent, bool tr, RendererAST* r) const
353     {
354         Shape child(parent);
355         switch (mRepType) {
356             case replacement:
357                 replace(child, r);
358                 child.mWorldState.mRand64Seed = r->mCurrentSeed;
359                 child.mWorldState.mRand64Seed();
360                 r->processShape(child);
361                 break;
362             case op:
363                 if (!tr)
364                     child.mWorldState.m_transform.reset();
365 	        FALLTHROUGH;
366             case mixed:
367             case command:
368                 replace(child, r);
369                 r->processSubpath(child, tr || (mRepType == op), mRepType);
370                 break;
371             default:
372                 throw CfdgError("Subpaths must be all path operation or all path command");
373         }
374     }
375 
376     void
traverse(const Shape & parent,bool tr,RendererAST * r) const377     ASTloop::traverse(const Shape& parent, bool tr, RendererAST* r) const
378     {
379         Shape loopChild(parent);
380         bool opsOnly = (mLoopBody.mRepType | mFinallyBody.mRepType) == op;
381         if (opsOnly && !tr)
382             loopChild.mWorldState.m_transform.reset();
383         double start, end, step;
384 
385         r->mCurrentSeed ^= mChildChange.modData.mRand64Seed;
386         if (mLoopArgs) {
387             setupLoop(start, end, step, mLoopArgs.get(), r);
388         } else {
389             start = mLoopData[0];
390             end = mLoopData[1];
391             step = mLoopData[2];
392         }
393         const StackType* oldTop = r->mLogicalStackTop;
394         if (r->mStackSize + 1 > r->mCFstack.size())
395             CfdgError::Error(mLocation, "Maximum stack depth exceeded");
396         StackType& index = r->mCFstack[r->mStackSize];
397         index.number = start;
398         ++r->mStackSize;
399         r->mLogicalStackTop = &index + 1;
400         for (;;) {
401             if (r->requestStop || Renderer::AbortEverything)
402                 throw CfdgError(mLocation, "Stopping");
403 
404             if (step > 0.0) {
405                 if (index.number >= end)
406                     break;
407             } else {
408                 if (index.number <= end)
409                     break;
410             }
411             mLoopBody.traverse(loopChild, tr || opsOnly, r);
412             mChildChange.evaluate(loopChild.mWorldState, true, r);
413             index.number += step;
414         }
415         mFinallyBody.traverse(loopChild, tr || opsOnly, r);
416         --r->mStackSize;
417         r->mLogicalStackTop = oldTop;
418     }
419 
420     void
traverse(const Shape & parent,bool tr,RendererAST * r) const421     ASTtransform::traverse(const Shape& parent, bool tr, RendererAST* r) const
422     {
423         static agg::trans_affine Dummy;
424         SymmList transforms;
425         std::vector<const ASTmodification*> mods = getTransforms(mExpHolder.get(), transforms, r, false, Dummy);
426 
427         Rand64 cloneSeed = r->mCurrentSeed;
428         Shape transChild(parent);
429         bool opsOnly = mBody.mRepType == op;
430         if (opsOnly && !tr)
431             transChild.mWorldState.m_transform.reset();
432 
433         std::size_t modsLength = mods.size();
434         std::size_t totalLength = modsLength + transforms.size();
435         for(std::size_t i = 0; i < totalLength; ++i) {
436             Shape child(transChild);
437             if (i < modsLength) {
438                 mods[i]->evaluate(child.mWorldState, true, r);
439             } else {
440                 child.mWorldState.m_transform.premultiply(transforms[i - modsLength]);
441             }
442             r->mCurrentSeed();
443 
444             // Specialized mBody.traverse() with cloning behavior
445             std::size_t s = r->mStackSize;
446             for (const rep_ptr& rep: mBody.mBody) {
447                 if (mClone)
448                     r->mCurrentSeed = cloneSeed;
449                 rep->traverse(child, opsOnly || tr, r);
450             }
451             r->unwindStack(s, mBody.mParameters);
452         }
453     }
454 
455     void
traverse(const Shape & parent,bool tr,RendererAST * r) const456     ASTif::traverse(const Shape& parent, bool tr, RendererAST* r) const
457     {
458         double cond = 0.0;
459         if (mCondition->evaluate(&cond, 1, r) != 1) {
460             CfdgError::Error(mLocation, "Error evaluating if condition");
461             return;
462         }
463         if (cond != 0.0) mThenBody.traverse(parent, tr, r);
464         else mElseBody.traverse(parent, tr, r);
465     }
466 
467     void
traverse(const Shape & parent,bool tr,RendererAST * r) const468     ASTswitch::traverse(const Shape& parent, bool tr, RendererAST* r) const
469     {
470         double caseValue = 0.0;
471         if (mSwitchExp->evaluate(&caseValue, 1, r) != 1) {
472             CfdgError::Error(mLocation, "Error evaluating switch selector");
473             return;
474         }
475 
476         caseType i = static_cast<caseType>(floor(caseValue));
477         caseRange cr{i, i};
478 
479         switchMap::const_iterator it = mCaseMap.find(cr);
480         if (it != mCaseMap.end()) (*it).second->traverse(parent, tr, r);
481         else mElseBody.traverse(parent, tr, r);
482     }
483 
484     void
traverse(const Shape & p,bool,RendererAST * r) const485     ASTdefine::traverse(const Shape& p, bool, RendererAST* r) const
486     {
487         if (mDefineType != StackDefine)
488             return;
489         if (r->mStackSize + mTuplesize > r->mCFstack.size())
490             CfdgError::Error(mLocation, "Maximum stack depth exceeded");
491         std::size_t s = r->mStackSize;
492         r->mStackSize += mTuplesize;
493         r->mCurrentSeed ^= mChildChange.modData.mRand64Seed;
494         StackType* dest = r->mCFstack.data() + s;
495 
496         switch (mType) {
497             case NumericType:
498                 if (mExpression->evaluate(&dest->number, mTuplesize, r) != mTuplesize)
499                     CfdgError::Error(mExpression->where,
500                                    "Error evaluating parameters (too many or not enough).");
501                 break;
502             case ModType: {
503                 Modification* smod = reinterpret_cast<Modification*> (dest);
504                 mChildChange.setVal(*smod, r);
505                 break;
506             }
507             case RuleType:
508                 new (&(dest->rule)) param_ptr(mExpression->evalArgs(r, p.mParameters.get()));
509                 break;
510             default:
511                 CfdgError::Error(mExpression->where, "Unimplemented parameter type.");
512                 break;
513         }
514 
515         r->mLogicalStackTop = r->mCFstack.data() + r->mStackSize;
516     }
517 
518     void
traverse(const Shape &,bool,RendererAST *) const519     ASTrule::traverse(const Shape&, bool, RendererAST*) const
520     {
521         assert(false);
522     }
523 
524     void
traverseRule(Shape & parent,RendererAST * r) const525     ASTrule::traverseRule(Shape& parent, RendererAST* r) const
526     {
527         r->mCurrentSeed = parent.mWorldState.mRand64Seed;
528 
529         if (isPath) {
530             r->processPrimShape(parent, this);
531         } else {
532             mRuleBody.traverse(parent, false, r, true);
533         }
534     }
535 
traverse(const Shape & s,bool tr,RendererAST * r) const536     void ASTpathOp::traverse(const Shape& s, bool tr, RendererAST* r) const
537     {
538         if (r->mCurrentPath->mCached)
539             return;
540         double opData[7];
541         pathData(opData, r);
542         r->mCurrentPath->addPathOp(this, opData, s, tr, r);
543     }
544 
545     void
traverse(const Shape & s,bool,RendererAST * r) const546     ASTpathCommand::traverse(const Shape& s, bool, RendererAST* r) const
547     {
548         if (r->mOpsOnly)
549             CfdgError::Error(mLocation, "Path commands not allowed at this point");
550 
551         Shape child(s);
552         double width = mStrokeWidth;
553         replace(child, r);
554         if (mParameters && mParameters->evaluate(&width, 1, r) != 1)
555             CfdgError::Error(mParameters->where, "Error computing stroke width");
556 
557         CommandInfo* info = nullptr;
558 
559         if (r->mCurrentPath->mCached) {
560             if (r->mCurrentCommand == r->mCurrentPath->mCommandInfo.end())
561                 CfdgError::Error(mLocation, "Not enough path commands in cache");
562             info = &(*(r->mCurrentCommand++));
563         } else {
564             if (r->mCurrentPath->mPath.total_vertices() == 0)
565                 CfdgError::Error(mLocation, "Path commands must be preceded by at least one path operation");
566 
567             r->mWantCommand = false;
568             r->mCurrentPath->finish(false, r);
569 
570             // Auto-align the previous set of paths ops unless the previous path
571             // command already auto-aligned them
572             if (r->mCurrentPath->mCommandInfo.empty() ||
573                 r->mCurrentPath->mCommandInfo.back().mIndex != r->mIndex)
574             {
575                 for (unsigned i = r->mIndex;
576                      i < r->mCurrentPath->mPath.total_vertices();
577                      i = r->mCurrentPath->mPath.align_path(i))
578                 { }
579             }
580 
581             mInfoCache.tryInit(r->mIndex, r->mCurrentPath.get(), width, this);
582             if (mInfoCache.mPathUID == r->mCurrentPath->mPathUID &&
583                 mInfoCache.mIndex   == r->mIndex)
584             {
585                 r->mCurrentPath->mCommandInfo.push_back(mInfoCache);
586             } else {
587                 r->mCurrentPath->mCommandInfo.emplace_back(r->mIndex, r->mCurrentPath.get(), width, this);
588             }
589             info = &(r->mCurrentPath->mCommandInfo.back());
590             info->mFlags |= child.mWorldState.m_BlendMode;
591         }
592 
593         r->processPathCommand(child, info);
594     }
595 
596     void
traversePath(const Shape & parent,RendererAST * r) const597     ASTrule::traversePath(const Shape& parent, RendererAST* r) const
598     {
599         r->init();
600         r->mCurrentSeed = parent.mWorldState.mRand64Seed;
601         r->mRandUsed = false;
602 
603         cpath_ptr savedPath;
604 
605         if (mCachedPath && StackRule::Equal(mCachedPath->mParameters.get(), parent.mParameters.get())) {
606             savedPath = std::move(r->mCurrentPath);
607             r->mCurrentPath = std::move(mCachedPath);
608             r->mCurrentCommand = r->mCurrentPath->mCommandInfo.begin();
609         } else {
610             r->mCurrentPath->mTerminalCommand.mLocation = mLocation;
611         }
612 
613         mRuleBody.traverse(parent, false, r, true);
614         if (!r->mCurrentPath->mCached)
615             r->mCurrentPath->finish(true, r);
616         if (r->mCurrentPath->mUseTerminal)
617             r->mCurrentPath->mTerminalCommand.traverse(parent, false, r);
618 
619         if (savedPath) {
620             mCachedPath = std::move(r->mCurrentPath);
621             r->mCurrentPath = std::move(savedPath);
622         } else {
623             if (!(r->mRandUsed) && !mCachedPath) {
624                 mCachedPath = std::move(r->mCurrentPath);
625                 mCachedPath->mCached = true;
626                 mCachedPath->mParameters = parent.mParameters;
627                 r->mCurrentPath = std::make_unique<ASTcompiledPath>();
628             } else {
629                 r->mCurrentPath->mPath.remove_all();
630                 r->mCurrentPath->mCommandInfo.clear();
631                 r->mCurrentPath->mUseTerminal = false;
632                 r->mCurrentPath->mPathUID = ASTcompiledPath::NextPathUID();
633                 r->mCurrentPath->mParameters.reset();
634             }
635         }
636     }
637 
638     void
compile(AST::CompilePhase ph,Builder * b)639     ASTreplacement::compile(AST::CompilePhase ph, Builder* b)
640     {
641         ASTexpression* r;
642         r = mShapeSpec.compile(ph, b);             // always returns nullptr
643         _unused(r);
644         assert(r == nullptr);
645         r = mChildChange.compile(ph, b);           // ditto
646         assert(r == nullptr);
647 
648         switch (ph) {
649             case CompilePhase::TypeCheck:
650                 mChildChange.addEntropy(mShapeSpec.entropyVal);
651                 if (typeid(ASTreplacement) == typeid(*this) && b->mInPathContainer) {
652                     // This is a subpath
653                     if (mShapeSpec.argSource == ASTruleSpecifier::ShapeArgs ||
654                         mShapeSpec.argSource == ASTruleSpecifier::StackArgs ||
655                         primShape::isPrimShape(mShapeSpec.shapeType))
656                     {
657                         if (mRepType != op)
658                             CfdgError::Error(mShapeSpec.where, "Error in subpath specification", b);
659                         if (mShapeSpec.shapeType == primShape::fillType)
660                             CfdgError::Error(mShapeSpec.where, "FILL cannot be a subpath", b);
661                     } else {
662                         const ASTrule* rule = b->GetRule(mShapeSpec.shapeType);
663                         if (!rule || !rule->isPath)
664                             CfdgError::Error(mShapeSpec.where, "Subpath can only refer to a path", b);
665                         else if (rule->mRuleBody.mRepType != mRepType)
666                             CfdgError::Error(mShapeSpec.where, "Subpath type mismatch error", b);
667                     }
668                 }
669                 break;
670             case CompilePhase::Simplify:
671                 r = mShapeSpec.simplify(b);          // always returns nullptr
672                 assert(r == nullptr);
673                 r = mChildChange.simplify(b);        // ditto
674                 assert(r == nullptr);
675                 break;
676         }
677     }
678 
679     void
compile(AST::CompilePhase ph,Builder * b)680     ASTloop::compile(AST::CompilePhase ph, Builder* b)
681     {
682         ASTreplacement::compile(ph, b);
683         Compile(mLoopArgs, ph, b);
684 
685         switch (ph) {
686             case CompilePhase::TypeCheck: {
687                 if (!mLoopArgs) {
688                     CfdgError::Error(mLocation, "A loop must have one to three index parameters.", b);
689                     return;
690                 }
691 
692                 std::string ent(mLoopName);
693                 mLoopArgs->entropy(ent);
694                 if (mLoopModHolder)
695                     mChildChange.addEntropy(ent);
696 
697                 bool bodyNatural = false;
698                 bool finallyNatural = false;
699                 Locality_t locality = mLoopArgs->mLocality;
700 
701                 int c = mLoopArgs->evaluate();
702                 if (c < 1 || c > 3) {
703                     CfdgError::Error(mLoopArgs->where, "A loop must have one to three index parameters.", b);
704                 }
705 
706                 if (mLoopArgs->isConstant) {
707                     bodyNatural = finallyNatural = mLoopArgs->isNatural;
708                 } else {
709                     std::size_t count = 0;
710                     for (auto&& loopArg: *mLoopArgs) {
711                         int num = loopArg.evaluate();
712                         switch (count) {
713                             case 0:
714                                 if (loopArg.isNatural)
715                                     bodyNatural = finallyNatural = true;
716                                 break;
717                             case 2: {
718                                 // Special case: if 1st & 2nd args are natural and 3rd
719                                 // is -1 then that is ok
720                                 double step;
721                                 if (loopArg.isConstant &&
722                                     loopArg.evaluate(&step, 1) == 1 &&
723                                     step == -1.0)
724                                 {
725                                     break;
726                                 }
727                             }   // else fall through
728                                 FALLTHROUGH;
729                             case 1:
730                                 if (!loopArg.isNatural)
731                                     bodyNatural = finallyNatural = false;
732                                 break;
733                             default:
734                                 break;
735                         }
736                         count += num;
737                     }
738                 }
739 
740                 mLoopBody.mParameters.front().isNatural = bodyNatural;
741                 mLoopBody.mParameters.front().mLocality = locality;
742                 mLoopBody.compile(ph, b, this, nullptr);
743                 mFinallyBody.mParameters.front().isNatural = finallyNatural;
744                 mFinallyBody.mParameters.front().mLocality = locality;
745                 mFinallyBody.compile(ph, b);
746 
747                 if (!mLoopModHolder)
748                     mChildChange.addEntropy(ent);
749                 break;
750             }
751             case CompilePhase::Simplify:
752                 Simplify(mLoopArgs, b);
753                 if (mLoopArgs->isConstant) {
754                     bool bodyNatural = mLoopBody.mParameters.front().isNatural;
755                     bool finallyNatural = mFinallyBody.mParameters.front().isNatural;
756                     setupLoop(mLoopData[0], mLoopData[1], mLoopData[2], mLoopArgs.get());
757                     bodyNatural = bodyNatural && mLoopData[0] == floor(mLoopData[0]) &&
758                                 mLoopData[1] == floor(mLoopData[1]) &&
759                                 mLoopData[2] == floor(mLoopData[2]) &&
760                                 mLoopData[0] >= 0.0 && mLoopData[1] >= 0.0 &&
761                                 mLoopData[0] < 9007199254740992. &&
762                                 mLoopData[1] < 9007199254740992.;
763                     finallyNatural = finallyNatural && bodyNatural &&
764                                 mLoopData[1] + mLoopData[2] >= -1.0 &&
765                                 mLoopData[1] + mLoopData[2] < 9007199254740992.;
766                     mLoopArgs.reset();
767                     mLoopBody.mParameters.front().isNatural = bodyNatural;
768                     mFinallyBody.mParameters.front().isNatural = finallyNatural;
769                 }
770                 mLoopBody.compile(ph, b);
771                 mFinallyBody.compile(ph, b);
772                 break;
773         }
774     }
775 
776     void
compileLoopMod(Builder * b)777     ASTloop::compileLoopMod(Builder* b)
778     {
779         if (mLoopModHolder) {
780             mLoopModHolder->compile(CompilePhase::TypeCheck, b);
781             mChildChange.grab(mLoopModHolder.get());
782         } else {
783             mChildChange.compile(CompilePhase::TypeCheck, b);
784         }
785     }
786 
787     void
compile(AST::CompilePhase ph,Builder * b)788     ASTtransform::compile(AST::CompilePhase ph, Builder* b)
789     {
790         ASTreplacement::compile(ph, b);
791         ASTexpression* ret = nullptr;
792         if (mExpHolder)
793             ret = mExpHolder->compile(ph, b);     // always returns nullptr
794         if (ret != nullptr)
795             CfdgError::Error(mLocation, "Error analyzing transform list", b);
796         mBody.compile(ph, b);
797 
798         switch (ph) {
799             case CompilePhase::TypeCheck:
800                 if (mClone && !b->impure())
801                     CfdgError::Error(mLocation, "Shape cloning only permitted in impure mode");
802                 break;
803             case CompilePhase::Simplify:
804                 Simplify(mExpHolder, b);
805                 break;
806         }
807     }
808 
809     void
compile(AST::CompilePhase ph,Builder * b)810     ASTif::compile(AST::CompilePhase ph, Builder* b)
811     {
812         ASTreplacement::compile(ph, b);
813         Compile(mCondition, ph, b);
814         mThenBody.compile(ph, b);
815         mElseBody.compile(ph, b);
816 
817         if (!mCondition) {
818             CfdgError::Error(mLocation, "If condition missing", b);
819             return;
820         }
821 
822         switch (ph) {
823             case CompilePhase::TypeCheck:
824                 if (mCondition->mType != NumericType || mCondition->evaluate() != 1)
825                     CfdgError::Error(mCondition->where, "If condition must be a numeric scalar", b);
826                 break;
827             case CompilePhase::Simplify:
828                 Simplify(mCondition, b);
829                 break;
830         }
831     }
832 
833     void
compile(AST::CompilePhase ph,Builder * b)834     ASTswitch::compile(AST::CompilePhase ph, Builder* b)
835     {
836         ASTreplacement::compile(ph, b);
837         Compile(mSwitchExp, ph, b);
838         for (auto&& _case: mCases) {
839             Compile(_case.first, ph, b);
840             _case.second->compile(ph, b);
841         }
842         mElseBody.compile(ph, b);
843 
844         if (!mSwitchExp) {
845             CfdgError::Error(mLocation, "Switch selector missing", b);
846             return;
847         }
848 
849         switch (ph) {
850             case CompilePhase::TypeCheck: {
851                 if (mSwitchExp->mType != NumericType || mSwitchExp->evaluate() != 1)
852                     CfdgError::Error(mSwitchExp->where, "Switch selector must be a numeric scalar", b);
853 
854                 // Build the switch map from the stored case value expressions
855                 double val[2] = { 0.0 };
856                 for (auto&& _case: mCases) {
857                     const ASTexpression* valExp = _case.first.get();
858                     if (!valExp) {
859                         CfdgError::Error(mLocation, "Case value missing", b);
860                         return;
861                     }
862                     ASTrepContainer* body = _case.second.get();
863                     for (auto&& term: *valExp) {
864                         const ASTfunction* func = dynamic_cast<const ASTfunction*>(&term);
865                         caseType high = 0, low = 0;
866                         try {
867                             if (func && func->functype == ASTfunction::RandOp) {
868                                 // The term is a range, get the bounds
869                                 if (func->arguments->evaluate(val, 2) != 2) {
870                                     CfdgError::Error(func->where, "Case range cannot be evaluated", b);
871                                     continue;
872                                 } else {
873                                     low = static_cast<caseType>(floor(val[0]));
874                                     high = static_cast<caseType>(floor(val[1]));
875                                     if (high <= low) {
876                                         CfdgError::Error(func->where, "Case range is reversed", b);
877                                         continue;
878                                     }
879                                 }
880                             } else {
881                                 // Not a range, must be a single value
882                                 if (term.evaluate(val, 1) != 1) {
883                                     CfdgError::Error(term.where, "Case value cannot be evaluated", b);
884                                     continue;
885                                 } else {
886                                     low = high = static_cast<caseType>(floor(val[0]));
887                                 }
888                             }
889 
890                             caseRange range{low, high};
891                             if (mCaseMap.count(range)) {
892                                 CfdgError::Error(term.where, "Case value already in use", b);
893                             } else {
894                                 mCaseMap[range] = body;
895                             }
896                         } catch (DeferUntilRuntime&) {
897                             CfdgError::Error(term.where, "Case expression is not constant", b);
898                         }
899                     }
900                 }
901                 break;
902             }
903             case CompilePhase::Simplify:
904                 Simplify(mSwitchExp, b);
905                 break;
906         }
907     }
908 
909     void
compile(AST::CompilePhase ph,Builder * b)910     ASTdefine::compile(AST::CompilePhase ph, Builder* b)
911     {
912         if (mDefineType == FunctionDefine || mDefineType == LetDefine) {
913             ASTrepContainer tempCont;
914             tempCont.mParameters = mParameters;     // copy
915             b->push_repContainer(tempCont);
916             ASTreplacement::compile(ph, b);
917             Compile(mExpression, ph, b);
918             if (ph == CompilePhase::Simplify)
919                 Simplify(mExpression, b);
920             b->pop_repContainer(nullptr);
921         } else {
922             ASTreplacement::compile(ph, b);
923             Compile(mExpression, ph, b);
924             if (ph == CompilePhase::Simplify)
925                 Simplify(mExpression, b);
926         }
927 
928         switch (ph) {
929             case CompilePhase::TypeCheck: {
930                 if (mDefineType == ConfigDefine) {
931                     b->TypeCheckConfig(this);
932                     return;
933                 }
934 
935                 // Set the Modification entropy to parameter name, not its own contents
936                 mChildChange.modData.mRand64Seed.seed();
937                 mChildChange.entropyIndex = 0;
938                 mChildChange.addEntropy(mName);
939 
940                 expType t = mExpression ? mExpression->mType : ModType;
941                 int sz = 1;
942                 if (t == NumericType)
943                     sz = mExpression->evaluate();
944                 if (t == ModType)
945                     sz = ModificationSize;
946                 if (mDefineType == FunctionDefine) {
947                     if (t != mType)
948                         CfdgError::Error(mLocation, "Mismatch between declared and defined type of user function", b);
949                     if (mType == NumericType && t == NumericType && sz != mTuplesize)
950                         CfdgError::Error(mLocation, "Mismatch between declared and defined vector length of user function", b);
951                     if (isNatural && (!mExpression || !mExpression->isNatural) && !b->impure())
952                         CfdgError::Error(mLocation, "Mismatch between declared natural and defined not-natural type of user function", b);
953                 } else {
954                     if (mShapeSpec.shapeType >= 0) {
955                         ASTdefine* func = nullptr;
956                         const ASTparameters* shapeParams = nullptr;
957                         b->GetTypeInfo(mShapeSpec.shapeType, func, shapeParams);
958                         if (func) {
959                             CfdgError::Error(mLocation, "Variable name is also the name of a function", b);
960                             CfdgError::Error(func->mLocation, "   function definition is here", b);
961                         }
962                         if (shapeParams)
963                             CfdgError::Error(mLocation, "Variable name is also the name of a shape", b);
964                     }
965 
966                     mTuplesize = sz;
967                     mType = t;
968                     if (t != (t & (-t)) || !t)
969                         CfdgError::Error(mLocation, "Expression can only have one type", b);
970                     if (mDefineType == StackDefine && (mExpression ? mExpression->isConstant : mChildChange.isConstant))
971                         mDefineType = ConstDefine;
972                     isNatural = mExpression && mExpression->isNatural && mType == NumericType;
973                     ASTparameter& param = b->mContainerStack.back()->
974                         addDefParameter(mShapeSpec.shapeType, this, mLocation, mLocation);
975                     if (mDefineType == StackDefine) {
976                         param.mStackIndex = b->mLocalStackDepth;
977                         b->mLocalStackDepth += param.mTuplesize;
978                     }
979                 }
980                 break;
981             }
982             case CompilePhase::Simplify:
983                 if (mDefineType == ConfigDefine)
984                     b->MakeConfig(this);
985                 break;
986         }
987     }
988 
989     void
compile(AST::CompilePhase ph,Builder * b)990     ASTrule::compile(AST::CompilePhase ph, Builder* b)
991     {
992         b->mInPathContainer = isPath;
993         ASTreplacement::compile(ph, b);
994         mRuleBody.compile(ph, b);
995         b->mInPathContainer = false;
996     }
997 
998     void
compile(AST::CompilePhase ph,Builder * b)999     ASTpathOp::compile(AST::CompilePhase ph, Builder* b)
1000     {
1001         ASTreplacement::compile(ph, b);
1002         Compile(mArguments, ph, b);
1003         if (mOldStyleArguments)
1004             mOldStyleArguments->compile(ph, b);     // always return nullptr
1005 
1006         switch (ph) {
1007             case CompilePhase::TypeCheck: {
1008                 if (mOldStyleArguments)
1009                     makePositional(b);
1010                 else
1011                     checkArguments(b);
1012                 break;
1013             }
1014             case CompilePhase::Simplify:
1015                 Simplify(mArguments, b);
1016                 pathDataConst(b);
1017                 break;
1018         }
1019     }
1020 
1021     void
to_json(json & j) const1022     ASTreplacement::to_json(json& j) const
1023     {
1024         j = json{
1025             {"class", "ASTreplacement"},
1026             {"replacement shape", mShapeSpec},
1027             {"replacement adjustment", mChildChange}
1028         };
1029     }
1030 
1031     void
to_json(json & j) const1032     ASTloop::to_json(json& j) const
1033     {
1034         j = json{
1035             {"class", "ASTloop"},
1036             {"loop variable name", CFDG::ShapeToString(mLoopIndexName)}
1037         };
1038         if (mLoopArgs) {
1039             json j2{};
1040             args_to_json(j2, *mLoopArgs);
1041             j["loop bounds"] = j2;
1042         } else {
1043             j["loop bounds"] = mLoopData;
1044         }
1045         j["loop modification"] = mChildChange;
1046         j["loop body"] = mLoopBody;
1047         j["finally body"] = mFinallyBody;
1048     }
1049 
1050     void
to_json(json & j) const1051     ASTtransform::to_json(json& j) const
1052     {
1053         j = json{
1054             {"class", mClone ? "ASTclone" : "ASTtransform"},
1055             {mClone ? "clone body" : "transform body", mBody}
1056         };
1057         json j2{};
1058         args_to_json(j2, *mExpHolder);
1059         j[mClone ? "clone list" : "transform list"] = j2;
1060     }
1061 
1062     void
to_json(json & j) const1063     ASTif::to_json(json& j) const
1064     {
1065         j = json{
1066             {"class", "ASTif"},
1067             {"if condition", *mCondition},
1068             {"then body", mThenBody},
1069             {"else body", mElseBody}
1070         };
1071     }
1072 
1073     void
to_json(json & j) const1074     ASTswitch::to_json(json& j) const
1075     {
1076         struct tempcase {
1077             std::vector<caseType> mCases;
1078             const ASTrepContainer* mCaseBody;
1079             tempcase(const ASTrepContainer* c) : mCaseBody(c) {}
1080             json to_json() const {
1081                 return json{{"cases", mCases}, {"case body", *mCaseBody}};
1082             }
1083         };
1084         std::vector<tempcase> tempCases;
1085         for (auto&& caseinfo: mCases)
1086             tempCases.emplace_back(caseinfo.second.get());
1087         for (auto&& caseinfo: mCaseMap) {
1088             for (auto&& tempcaseinfo: tempCases)
1089                 if (caseinfo.second == tempcaseinfo.mCaseBody) {
1090                     for (auto i = caseinfo.first.first; i <= caseinfo.first.second; ++i)
1091                         tempcaseinfo.mCases.push_back(i);
1092                     break;
1093                 }
1094         }
1095         j = json{
1096             {"class", "ASTswitch"},
1097             {"switch expression", *mSwitchExp},
1098             {"switch cases", json::array()},
1099             {"switch else body", mElseBody}
1100         };
1101         for (auto&& c: tempCases)
1102             j["switch cases"].push_back(c.to_json());
1103     }
1104 
1105     void
to_json(json & j) const1106     ASTdefine::to_json(json& j) const
1107     {
1108         static std::map<define_t, std::string> defTypeName =
1109         {
1110             {StackDefine, "stack definition"},
1111             {ConstDefine, "constant definition"},
1112             {ConfigDefine, "configuration definition"},
1113             {FunctionDefine, "function definition"},
1114             {LetDefine, "let definition"}
1115         };
1116         j = {
1117             {"class", "ASTdefine"},
1118             {"definition type", defTypeName[mDefineType]},
1119             {"definition name", mDefineType == ConfigDefine ? mName : CFDG::ShapeToString(mShapeSpec.shapeType)}
1120         };
1121         if (mDefineType == FunctionDefine) {
1122             j["function parameters"] = mParameters;
1123             j["function expression"] = *mExpression;
1124         } else if (mDefineType != ConfigDefine) {
1125             if (mExpression)
1126                 j["definition expression"] = *mExpression;
1127             else
1128                 j["definition adjustment"] = mChildChange;
1129         }
1130         if (mExpression && mExpression->mType == NumericType)
1131             j["length"] = mTuplesize;
1132     }
1133 
1134     void
to_json(json & j) const1135     ASTrule::to_json(json& j) const
1136     {
1137         if (isPath) {
1138             j = {
1139                 {"class", "ASTpath"},
1140                 {"path name", CFDG::ShapeToString(mNameIndex)}
1141             };
1142             if (auto params = CFDG::ShapeToParams(mNameIndex))
1143                 j["path parameters"] = *params;
1144             else
1145                 j["path parameters"] = json::array();
1146             j["path body"] = mRuleBody;
1147         } else {
1148             j = {
1149                 {"class", "ASTrule"},
1150                 {"rule name", CFDG::ShapeToString(mNameIndex)},
1151                 {"rule weight", mWeight}
1152             };
1153             if (auto params = CFDG::ShapeToParams(mNameIndex))
1154                 j["rule parameters"] = *params;
1155             else
1156                 j["rule parameters"] = json::array();
1157             j["rule body"] = mRuleBody;
1158         }
1159     }
1160 
1161     void
to_json(json & j) const1162     ASTpathOp::to_json(json& j) const
1163     {
1164         static const std::map<pathOpEnum, std::string> pathOpNames =
1165         {
1166             {unknownPathop, "unknown"},
1167             {MOVETO, "MOVETO"},
1168             {MOVEREL, "MOVEREL"},
1169             {LINETO, "LINETO"},
1170             {LINEREL, "LINEREL"},
1171             {ARCTO, "ARCTO"},
1172             {ARCREL, "ARCREL"},
1173             {CURVETO, "CURVETO"},
1174             {CURVEREL, "CURVEREL"},
1175             {CLOSEPOLY, "CLOSEPOLY"}
1176         };
1177         try {
1178             auto pathop = pathOpNames.at(mPathOp);
1179             j = {
1180                 {"class", "ASTpathOp"},
1181                 {"path op", pathop}
1182             };
1183             if (mArguments) {
1184                 json j2{};
1185                 args_to_json(j2, *mArguments);
1186                 j["path op arguments"] = j2;
1187             } else {
1188                 std::vector<double> data(6);
1189                 mChildChange.modData.m_transform.store_to(data.data());
1190                 data.resize(mArgCount);
1191                 j["path op arguments"] = data;
1192             }
1193             json_string flags;
1194             if (mFlags & CF_ARC_CW)
1195                 flags = "CF::ArcCW";
1196             if (mFlags & CF_ARC_LARGE)
1197                 flags += "CF::ArcLarge";
1198             if (mFlags & CF_CONTINUOUS)
1199                 flags += "CF::Continuous";
1200             if (mFlags & CF_ALIGN)
1201                 flags += "CF::Align";
1202             j["path op flags"] = flags.get();
1203         } catch (std::out_of_range&) {}
1204     }
1205 
1206     void
to_json(json & j) const1207     ASTpathCommand::to_json(json& j) const
1208     {
1209         json_string flags;
1210         if (mFlags & CF_FILL) {
1211             if (mFlags & CF_EVEN_ODD)
1212                 flags = "CF::EvenOdd";
1213             j = json{
1214                 {"class", "ASTpathCommand"},
1215                 {"path command", "FILL"},
1216                 {"adjustment", mChildChange},
1217                 {"flags", flags.get()}
1218             };
1219         } else {
1220             static const char* joinNames[8] = {"CF::MiterJoin", "???", "CF::RoundJoin", "CF::BevelJoin", "???"};
1221             static const char* capNames[8] = {"CF::ButtCap", "CF::SquareCap", "CF::RoundCap", "???"};
1222             flags = joinNames[mFlags & 7];
1223             flags += capNames[(mFlags >> 4) & 7];
1224             if (mFlags & CF_ISO_WIDTH)
1225                 flags += "CF::IsoWidth";
1226             j = json{
1227                 {"class", "ASTpathCommand"},
1228                 {"path command", "STROKE"},
1229                 {"adjustment", mChildChange},
1230                 {"flags", flags.get()}
1231             };
1232             if (mParameters)
1233                 j["stroke width"] = *mParameters;
1234             else
1235                 j["stroke width"] = mStrokeWidth;
1236         }
1237     }
1238 
GetFlagsAndStroke(ASTtermArray & terms,int & flags,Builder * b)1239     static exp_ptr GetFlagsAndStroke(ASTtermArray& terms, int& flags, Builder* b)
1240     {
1241         ASTtermArray temp(std::move(terms));
1242         exp_ptr ret;
1243 
1244         for (term_ptr& term: temp) {
1245             switch (term->modType) {
1246                 case AST::ASTmodTerm::param:
1247                     flags |= term->flags;       // ctor stashes parsed params here and
1248                     break;                      // ASTmodTerm::compile() does not overwrite them
1249                 case AST::ASTmodTerm::stroke:
1250                     if (ret)
1251                         CfdgError::Error(term->where, "Only one stroke width term is allowed", b);
1252                     ret = std::move(term->args);
1253                     break;
1254                 default:
1255                     terms.emplace_back(std::move(term));
1256                     break;
1257             }
1258         }
1259 
1260         return ret;
1261     }
1262 
1263     void
compile(AST::CompilePhase ph,Builder * b)1264     ASTpathCommand::compile(AST::CompilePhase ph, Builder* b)
1265     {
1266         ASTreplacement::compile(ph, b);
1267         Compile(mParameters, ph, b);
1268 
1269         switch (ph) {
1270             case CompilePhase::TypeCheck: {
1271                 mChildChange.addEntropy((mFlags & CF_FILL) ? "FILL" : "STROKE");
1272 
1273                 // Extract any stroke adjustments
1274                 exp_ptr w = GetFlagsAndStroke(mChildChange.modExp, mFlags, b);
1275                 if (w) {
1276                     if (mParameters)
1277                         CfdgError::Error(w->where, "Cannot have a stroke adjustment in a v3 path command", b);
1278                     else if (w->size() != 1 || w->mType != NumericType || w->evaluate() != 1)
1279                         CfdgError::Error(w->where, "Stroke adjustment is ill-formed", b);
1280                     else
1281                         mParameters = std::move(w);
1282                 }
1283 
1284                 if (!mParameters)
1285                     return;
1286 
1287                 exp_ptr stroke, flags;
1288                 yy::location loc = mParameters->where;
1289                 ASTexpArray pcmdParams = Extract(std::move(mParameters));
1290                 switch (pcmdParams.size()) {
1291                     case 2:
1292                         stroke = std::move(pcmdParams[0]);
1293                         flags = std::move(pcmdParams[1]);
1294                         break;
1295                     case 1:
1296                         switch (pcmdParams[0]->mType) {
1297                             case NumericType:
1298                                 stroke = std::move(pcmdParams[0]);
1299                                 break;
1300                             case FlagType:
1301                                 flags = std::move(pcmdParams[0]);
1302                                 break;
1303                             default:
1304                                 CfdgError::Error(loc, "Bad expression type in path command parameters", b);
1305                                 break;
1306                         }
1307                         break;
1308                     case 0:
1309                         return;
1310                     default:
1311                         CfdgError::Error(loc, "Path commands can have zero, one, or two parameters", b);
1312                         return;
1313                 }
1314 
1315                 if (stroke) {
1316                     if (mFlags & CF_FILL)
1317                         CfdgError::Warning(stroke->where, "Stroke width only useful for STROKE commands");
1318                     if (stroke->mType != NumericType || stroke->evaluate() != 1) {
1319                         CfdgError::Error(stroke->where, "Stroke width expression must be numeric scalar", b);
1320                     } else if (!stroke->isConstant ||
1321                                stroke->evaluate(&mStrokeWidth, 1) != 1)
1322                     {
1323                         mParameters = std::move(stroke);
1324                     }
1325                 }
1326 
1327                 if (flags) {
1328                     if (flags->mType != FlagType) {
1329                         CfdgError::Error(flags->where, "Unexpected argument in path command", b);
1330                         return;
1331                     }
1332                     Simplify(flags, b);
1333                     if (ASTreal* r = dynamic_cast<ASTreal*> (flags.get())) {
1334                         int f = static_cast<int>(r->value);
1335                         if (f & CF_JOIN_PRESENT)
1336                             mFlags &= ~CF_JOIN_MASK;
1337                         if (f & CF_CAP_PRESENT)
1338                             mFlags &= ~CF_CAP_MASK;
1339                         mFlags |= f;
1340                         if ((mFlags & CF_FILL) && (f & (CF_JOIN_PRESENT | CF_CAP_PRESENT)))
1341                             CfdgError::Warning(flags->where, "Stroke flags only useful for STROKE commands");
1342                     } else {
1343                         CfdgError::Error(flags->where, "Flag expressions must be constant", b);
1344                     }
1345                 }
1346                 break;
1347             }
1348             case CompilePhase::Simplify:
1349                 Simplify(mParameters, b);
1350                 break;
1351         }
1352     }
1353 
1354     void
addPathOp(const ASTpathOp * pop,double data[6],const Shape & s,bool tr,RendererAST * r)1355     ASTcompiledPath::addPathOp(const ASTpathOp* pop, double data[6], const Shape& s,
1356                                bool tr, RendererAST* r)
1357     {
1358         // Process the parameters for ARCTO/ARCREL
1359         double radius_x = 0.0, radius_y = 0.0, angle = 0.0;
1360         bool sweep = (pop->mFlags & CF_ARC_CW) == 0;
1361         bool largeArc = (pop->mFlags & CF_ARC_LARGE) != 0;
1362         if (pop->mPathOp == ARCTO || pop->mPathOp == ARCREL) {
1363             if (pop->mArgCount == 5) {
1364                 // If the radii are specified then use the ellipse ARCxx form
1365                 radius_x = data[2];
1366                 radius_y = data[3];
1367                 angle = data[4] * 0.0174532925199;
1368             } else {
1369                 // Otherwise use the circle ARCxx form
1370                 radius_x = radius_y = data[2];
1371                 angle = 0.0;
1372             }
1373             if (radius_x < 0.0 || radius_y < 0.0) {
1374                 radius_x = fabs(radius_x);
1375                 radius_y = fabs(radius_y);
1376                 sweep = !sweep;
1377             }
1378         } else if (tr) {
1379             s.mWorldState.m_transform.transform(data + 0, data + 1);
1380             s.mWorldState.m_transform.transform(data + 2, data + 3);
1381             s.mWorldState.m_transform.transform(data + 4, data + 5);
1382         }
1383 
1384         // If this is the first path operation following a path command then set the
1385         // path index used by subsequent path commands to the path sequence that the
1386         // current path operation is part of.
1387         // If this is not the first path operation following a path command then this
1388         // line does nothing.
1389         r->mIndex = r->mNextIndex;
1390 
1391         // If the op is anything other than a CLOSEPOLY then we are opening up a
1392         // new path sequence.
1393         r->mClosed = false;
1394         r->mStop = false;
1395 
1396         // This new path op needs to be covered by a command, either from the cfdg
1397         // file or default.
1398         r->mWantCommand = true;
1399 
1400         if (pop->mPathOp == CLOSEPOLY) {
1401             if (mPath.total_vertices() > 1 &&
1402                 agg::is_drawing(mPath.vertices().last_command()))
1403             {
1404                 // Find the MOVETO/MOVEREL that is the start of the current path sequence
1405                 // and reset LastPoint to that.
1406                 unsigned last = mPath.total_vertices() - 1;
1407                 unsigned cmd = agg::path_cmd_stop;
1408                 for (unsigned i = last - 1;
1409                      i < last && agg::is_vertex(cmd = mPath.command(i));
1410                      --i)
1411                 {
1412                     if (agg::is_move_to(cmd)) {
1413                         mPath.vertex(i, &(r->mLastPoint.x), &(r->mLastPoint.y));
1414                         break;
1415                     }
1416                 }
1417 
1418                 if (!agg::is_move_to(cmd))
1419                     CfdgError::Error(pop->mLocation, "CLOSEPOLY: Unable to find a MOVETO/MOVEREL for start of path.");
1420 
1421                 // If this is an aligning CLOSEPOLY then change the last vertex to
1422                 // exactly match the first vertex in the path sequence
1423                 if (pop->mFlags & CF_ALIGN) {
1424                     mPath.modify_vertex(last, r->mLastPoint.x, r->mLastPoint.y);
1425                 }
1426             } else if (pop->mFlags & CF_ALIGN) {
1427                 CfdgError::Error(pop->mLocation, "Nothing to align to.");
1428             }
1429             mPath.close_polygon();
1430             r->mClosed = true;
1431             r->mWantMoveTo = true;
1432             return;
1433         }
1434 
1435         // Insert an implicit MOVETO unless the pathOp is a MOVETO/MOVEREL
1436         if (r->mWantMoveTo && pop->mPathOp > MOVEREL) {
1437             r->mWantMoveTo = false;
1438             mPath.move_to(r->mLastPoint.x, r->mLastPoint.y);
1439         }
1440 
1441         switch (pop->mPathOp) {
1442             case MOVEREL:
1443                 mPath.rel_to_abs(data + 0, data + 1);
1444 		FALLTHROUGH;
1445             case MOVETO:
1446                 mPath.move_to(data[0], data[1]);
1447                 r->mWantMoveTo = false;
1448                 break;
1449             case LINEREL:
1450                 mPath.rel_to_abs(data + 0, data + 1);
1451 		FALLTHROUGH;
1452             case LINETO:
1453                 mPath.line_to(data[0], data[1]);
1454                 break;
1455             case ARCREL:
1456                 mPath.rel_to_abs(data + 0, data + 1);
1457 		FALLTHROUGH;
1458             case ARCTO: {
1459                 if (!agg::is_vertex(mPath.last_vertex(data + 2, data + 3)) ||
1460                     (tr && s.mWorldState.m_transform.determinant() < 1e-10))
1461                 {
1462                     break;
1463                 }
1464 
1465                 // Transforming an arc as they are parameterized by AGG is VERY HARD.
1466                 // So instead we insert the arc and then transform the bezier curves
1467                 // that are used to approximate the arc. But first we have to inverse
1468                 // transform the starting point to match the untransformed arc.
1469                 // Afterwards the starting point is restored to its original value.
1470                 if (tr) {
1471                     unsigned start = mPath.total_vertices() - 1;
1472                     agg::trans_affine inverseTr = ~s.mWorldState.m_transform;
1473                     mPath.transform(inverseTr, start);
1474                     mPath.arc_to(radius_x, radius_y, angle, largeArc, sweep, data[0], data[1]);
1475                     mPath.modify_vertex(start, data[2], data[3]);
1476                     mPath.transform(s.mWorldState.m_transform, start + 1);
1477                 } else {
1478                     mPath.arc_to(radius_x, radius_y, angle, largeArc, sweep, data[0], data[1]);
1479                 }
1480                 break;
1481             }
1482             case CURVEREL:
1483                 mPath.rel_to_abs(data + 0, data + 1);
1484                 mPath.rel_to_abs(data + 2, data + 3);
1485                 mPath.rel_to_abs(data + 4, data + 5);
1486 		FALLTHROUGH;
1487             case CURVETO:
1488                 if ((pop->mFlags & CF_CONTINUOUS) &&
1489                     !agg::is_curve(mPath.last_vertex(data + 4, data + 5)))
1490                 {
1491                     CfdgError::Error(pop->mLocation, "Smooth curve operations must be preceded by another curve operation.");
1492                     break;
1493                 }
1494                 switch (pop->mArgCount) {
1495                     case 2:
1496                         mPath.curve3(data[0], data[1]);
1497                         break;
1498                     case 4:
1499                         if (pop->mFlags & CF_CONTINUOUS)
1500                             mPath.curve4(data[2], data[3], data[0], data[1]);
1501                         else
1502                             mPath.curve3(data[2], data[3], data[0], data[1]);
1503                         break;
1504                     case 6:
1505                         mPath.curve4(data[2], data[3], data[4], data[5], data[0], data[1]);
1506                         break;
1507                     default:
1508                         break;
1509                 }
1510                 break;
1511             default:
1512                 break;
1513         }
1514 
1515         mPath.last_vertex(&(r->mLastPoint.x), &(r->mLastPoint.y));
1516     }
1517 
1518     void
finish(bool setAttr,RendererAST * r)1519     ASTcompiledPath::finish(bool setAttr, RendererAST* r)
1520     {
1521         // Close and end the last path sequence if it wasn't already closed and ended
1522         if (!r->mClosed) {
1523             mPath.end_poly(0);
1524             r->mClosed = true;
1525         }
1526 
1527         if (!r->mStop) {
1528             mPath.start_new_path();
1529             r->mStop = true;
1530         }
1531 
1532         r->mWantMoveTo = true;
1533         r->mNextIndex = mPath.total_vertices();
1534 
1535         // If setAttr is true then make sure that the last path sequence has a path
1536         // attribute associated with it.
1537         if (setAttr && r->mWantCommand) {
1538             mUseTerminal = true;
1539             r->mWantCommand = false;
1540         }
1541     }
1542 
1543     UIDdatatype
NextPathUID()1544     ASTcompiledPath::NextPathUID()
1545     {
1546         return ++GlobalPathUID;
1547     }
1548 
1549     bool
compareLT(const ASTrule * a,const ASTrule * b)1550     ASTrule::compareLT(const ASTrule* a, const ASTrule* b)
1551     {
1552         return a->mNameIndex < b->mNameIndex || (a->mNameIndex == b->mNameIndex &&
1553                                                  a->mWeight < b->mWeight);
1554     }
1555 
1556     void
pathData(double * data,RendererAST * rti) const1557     ASTpathOp::pathData(double* data, RendererAST* rti) const
1558     {
1559         if (mArguments) {
1560             if (mArguments->evaluate(data, 7, rti) < 0)
1561                 CfdgError::Error(mArguments->where, "Cannot evaluate arguments");
1562         } else {
1563             mChildChange.modData.m_transform.store_to(data);
1564         }
1565     }
1566 
1567     void
pathDataConst(Builder * b)1568     ASTpathOp::pathDataConst(Builder* b)
1569     {
1570         if (mArguments && mArguments->isConstant) {
1571             double data[7];
1572             if (mArguments->evaluate(data, 7) < 0)
1573                 CfdgError::Error(mArguments->where, "Cannot evaluate arguments", b);
1574             mArguments.reset();
1575             mChildChange.modData.m_transform.load_from(data);
1576         }
1577     }
1578 
1579     void
checkArguments(Builder * b)1580     ASTpathOp::checkArguments(Builder* b)
1581     {
1582         if (mArguments) {
1583             mArgCount = mArguments->evaluate();
1584 
1585             const ASTexpression* flag = nullptr;
1586             for (auto&& arg: *mArguments) {
1587                 switch (arg.mType) {
1588                     case FlagType: {
1589                         if (flag)
1590                             CfdgError::Error(flag->where, "There can only be one flag argument", b);
1591                         flag = &arg;
1592                         if (const ASTreal* rf = dynamic_cast<const ASTreal*> (&arg))
1593                             mFlags |= rf ? static_cast<int>(rf->value) : 0;
1594                         else
1595                             CfdgError::Error(arg.where, "Flag expressions must be constant", b);
1596                         --mArgCount;    // don't count flags
1597                         break;
1598                     }
1599                     case NumericType:
1600                         if (flag) {
1601                             CfdgError::Error(flag->where, "Flags must be the last argument", b);
1602                             flag = nullptr;
1603                         }
1604                         break;
1605                     default:
1606                         CfdgError::Error(arg.where, "Path operation arguments must be numeric expressions or flags", b);
1607                         break;
1608                 }
1609             }
1610         }
1611 
1612         switch (mPathOp) {
1613             case LINETO:
1614             case LINEREL:
1615             case MOVETO:
1616             case MOVEREL:
1617                 if (mArgCount != 2)
1618                     CfdgError::Error(mLocation, "Move/line path operation requires two arguments", b);
1619                 break;
1620             case ARCTO:
1621             case ARCREL:
1622                 if (mArgCount != 3 && mArgCount != 5)
1623                     CfdgError::Error(mLocation, "Arc path operations require three or five arguments", b);
1624                 break;
1625             case CURVETO:
1626             case CURVEREL:
1627                 if (mFlags & CF_CONTINUOUS) {
1628                     if (mArgCount != 2 && mArgCount != 4)
1629                         CfdgError::Error(mLocation, "Continuous curve path operations require two or four arguments", b);
1630                 } else {
1631                     if (mArgCount != 4 && mArgCount != 6)
1632                         CfdgError::Error(mLocation, "Non-continuous curve path operations require four or six arguments", b);
1633                 }
1634                 break;
1635             case CLOSEPOLY:
1636                 if (mArgCount)
1637                     CfdgError::Error(mLocation, "CLOSEPOLY takes no arguments, only flags", b);
1638                 break;
1639             default:
1640                 break;
1641         }
1642     }
1643 
1644     static ASTexpression*
parseXY(exp_ptr ax,exp_ptr ay,double def,const yy::location & loc,Builder * b)1645     parseXY(exp_ptr ax, exp_ptr ay, double def, const yy::location& loc, Builder* b)
1646     {
1647         if (!ax)
1648             ax = std::make_unique<ASTreal>(def, loc);
1649         int sz = 0;
1650         if (ax->mType == NumericType)
1651             sz = ax->evaluate();
1652         else
1653             CfdgError::Error(ax->where, "Path argument must be a scalar value", b);
1654 
1655         if (sz == 1 && !ay)
1656             ay = std::make_unique<ASTreal>(def, loc);
1657 
1658         if (ay && sz >= 0) {
1659             if (ay->mType == NumericType)
1660                 sz += ay->evaluate();
1661             else
1662                 CfdgError::Error(ay->where, "Path argument must be a scalar value", b);
1663         }
1664 
1665         if (sz != 2)
1666             CfdgError::Error(loc, "Error parsing path operation arguments", b);
1667 
1668         return ax.release()->append(ay.release());
1669     }
1670 
1671     static void
rejectTerm(exp_ptr & term,Builder * b)1672     rejectTerm(exp_ptr& term, Builder* b)
1673     {
1674         if (term)
1675             CfdgError::Error(term->where, "Illegal argument", b);
1676     }
1677 
1678     static void
acquireTerm(exp_ptr & exp,term_ptr & term,Builder * b)1679     acquireTerm(exp_ptr& exp, term_ptr& term, Builder* b)
1680     {
1681         if (exp) {
1682             CfdgError::Error(exp->where, "Duplicate argument", b);
1683             CfdgError::Error(term->where, "    conflicts with this argument", b);
1684         }
1685         exp = std::move(term->args);
1686     }
1687 
1688     void
makePositional(Builder * b)1689     ASTpathOp::makePositional(Builder* b)
1690     {
1691         if (!mOldStyleArguments) {
1692             CfdgError::Error(mLocation, "Path operation arguments missing");
1693             return;
1694         }
1695         exp_ptr w = GetFlagsAndStroke(mOldStyleArguments->modExp, mFlags, b);
1696         if (w)
1697             CfdgError::Error(w->where, "Stroke width not allowed in a path operation", b);
1698 
1699         exp_ptr ax;
1700         exp_ptr ay;
1701         exp_ptr ax1;
1702         exp_ptr ay1;
1703         exp_ptr ax2;
1704         exp_ptr ay2;
1705         exp_ptr arx;
1706         exp_ptr ary;
1707         exp_ptr ar;
1708 
1709         for (term_ptr& mod: mOldStyleArguments->modExp) {
1710             if (!mod)
1711                 continue;
1712             switch (mod->modType) {
1713                 case ASTmodTerm::x:
1714                     acquireTerm(ax, mod, b);
1715                     break;
1716                 case ASTmodTerm::y:
1717                     acquireTerm(ay, mod, b);
1718                     break;
1719                 case ASTmodTerm::x1:
1720                     acquireTerm(ax1, mod, b);
1721                     break;
1722                 case ASTmodTerm::y1:
1723                     acquireTerm(ay1, mod, b);
1724                     break;
1725                 case ASTmodTerm::x2:
1726                     acquireTerm(ax2, mod, b);
1727                     break;
1728                 case ASTmodTerm::y2:
1729                     acquireTerm(ay2, mod, b);
1730                     break;
1731                 case ASTmodTerm::xrad:
1732                     acquireTerm(arx, mod, b);
1733                     break;
1734                 case ASTmodTerm::yrad:
1735                     acquireTerm(ary, mod, b);
1736                     break;
1737                 case ASTmodTerm::rot:
1738                     acquireTerm(ar, mod, b);
1739                     break;
1740                 case ASTmodTerm::z:
1741                 case ASTmodTerm::zsize:
1742                     CfdgError::Error(mod->where, "Z changes are not permitted in paths", b);
1743                     break;
1744                 case ASTmodTerm::unknownType:
1745                 default:
1746                     CfdgError::Error(mod->where, "Unrecognized element in a path operation", b);
1747                     break;
1748             }
1749         }
1750 
1751         ASTexpression* xy = nullptr;
1752         if (mPathOp != CLOSEPOLY) {
1753             xy = parseXY(std::move(ax), std::move(ay), 0.0, mLocation, b);
1754         }
1755 
1756         switch (mPathOp) {
1757             case LINETO:
1758             case LINEREL:
1759             case MOVETO:
1760             case MOVEREL:
1761                 mArguments.reset(xy);
1762                 break;
1763             case ARCTO:
1764             case ARCREL:
1765                 if (arx || ary) {
1766                     ASTexpression* rxy = parseXY(std::move(arx), std::move(ary), 1.0, mLocation, b);
1767                     ASTexpression* angle = ar.release();
1768                     if (!angle)
1769                         angle = new ASTreal(0.0, mLocation);
1770 
1771                     if (angle->mType != NumericType || angle->evaluate() != 1)
1772                         CfdgError::Error(angle->where, "Arc angle must be a scalar value", b);
1773 
1774                     mArguments.reset(xy->append(rxy)->append(angle));
1775                 } else {
1776                     ASTexpression* radius = ar.release();
1777                     if (!radius)
1778                         radius = new ASTreal(1.0, mLocation);
1779 
1780                     if (radius->mType != NumericType || radius->evaluate() != 1)
1781                         CfdgError::Error(radius->where, "Arc radius must be a scalar value", b);
1782 
1783                     mArguments.reset(xy->append(radius));
1784                 }
1785                 break;
1786             case CURVETO:
1787             case CURVEREL: {
1788                 ASTexpression *xy1 = nullptr, *xy2 = nullptr;
1789                 if (ax1 || ay1) {
1790                     xy1 = parseXY(std::move(ax1), std::move(ay1), 0.0, mLocation, b);
1791                 } else {
1792                     mFlags |= CF_CONTINUOUS;
1793                 }
1794                 if (ax2 || ay2) {
1795                     xy2 = parseXY(std::move(ax2), std::move(ay2), 0.0, mLocation, b);
1796                 }
1797 
1798                 mArguments.reset(xy->append(xy1)->append(xy2));
1799                 break;
1800             }
1801             case CLOSEPOLY:
1802                 break;
1803             default:
1804                 break;
1805         }
1806 
1807         rejectTerm(ax, b);
1808         rejectTerm(ay, b);
1809         rejectTerm(ar, b);
1810         rejectTerm(arx, b);
1811         rejectTerm(ary, b);
1812         rejectTerm(ax1, b);
1813         rejectTerm(ay1, b);
1814         rejectTerm(ax2, b);
1815         rejectTerm(ay2, b);
1816 
1817         mArgCount = mArguments ? mArguments->evaluate() : 0;
1818         mOldStyleArguments.reset();
1819     }
1820 }
1821