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