diff --git a/README.md b/README.md
index 569bb63..343b7b1 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ The following turtle commands are currently supported by lindsvg:
| `+` | Turn left by turning angle (theta) |
| `-` | Turn right by turning angle (theta) |
| `\|` | Reverse direction (turn by 180 degrees) |
+| `!` | Reverse the meaning of `+` and `-` |
| `[` | Push current state of the turtle onto the stack |
| `]` | Pop a state from the stack and apply it to the turtle |
| `A`,`C`–`E`,`G`–`Z` | Auxiliary user-defined rules |
diff --git a/dist/lindsvg.cjs b/dist/lindsvg.cjs
index 017af5d..a2eed52 100644
--- a/dist/lindsvg.cjs
+++ b/dist/lindsvg.cjs
@@ -1,13 +1,13 @@
/*!
-lindsvg v1.4.0
+lindsvg v1.5.0
https://amphiluke.github.io/lindsvg/
(c) 2024 Amphiluke
*/
'use strict';
let messages = {
- AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,[,]",
- RULE: "Production rules may only contain the following characters: A..Z,+,-,|,[,]",
+ AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",
+ RULE: "Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",
LETTER: "Allowed alphabet letters are: A..Z",
ALPHA: "The “alpha” parameter must be a finite number",
THETA: "The “theta” parameter must be a finite number",
@@ -21,7 +21,7 @@ function checkLetter(letter, msg = messages.LETTER) {
return letterRE.test(letter) || msg;
}
-let ruleRE = /^[A-Z+\-[\]|]*$/;
+let ruleRE = /^[A-Z+\-|![\]]*$/;
function checkRule(rule, msg = messages.RULE) {
return ruleRE.test(rule) || msg;
}
@@ -118,6 +118,7 @@ let ctrlRules = {
"+": "+",
"-": "-",
"|": "|",
+ "!": "!",
"[": "[",
"]": "]",
};
@@ -137,7 +138,7 @@ let defaults = {
*/
function cleanCodeword(codeword) {
// Remove auxiliary drawing-indifferent letters
- let cleanCodeword = codeword.replace(/[^FB[\]+-|]/g, "");
+ let cleanCodeword = codeword.replace(/[^FB[\]+-|!]/g, "");
do {
codeword = cleanCodeword;
// Remove useless brackets that don’t contain F commands or other brackets (preserving bracket balance!)
@@ -170,7 +171,7 @@ function generateCodeword(lsParams) {
* @return {String[]}
*/
function tokenizeCodeword(codeword) {
- return codeword.match(/([FB[\]+-|])\1*/g); // tokenize
+ return codeword.match(/([FB[\]+-|!])\1*/g); // tokenize
}
class Turtle {
@@ -200,6 +201,10 @@ class Turtle {
this.alpha += (repeatCount % 2) * Math.PI;
}
+ swapSigns(repeatCount = 1) {
+ this.theta *= (-1) ** repeatCount;
+ }
+
pushStack(repeatCount = 1) {
for (; repeatCount > 0; repeatCount--) {
this.stack.push({x: this.x, y: this.y, alpha: this.alpha});
@@ -271,6 +276,9 @@ function getPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
turtle.pushStack(tokenLength);
break;
@@ -323,6 +331,9 @@ function getMultiPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
branchLevel += tokenLength;
turtle.pushStack(tokenLength);
diff --git a/dist/lindsvg.esm.js b/dist/lindsvg.esm.js
index 6716570..12bca27 100644
--- a/dist/lindsvg.esm.js
+++ b/dist/lindsvg.esm.js
@@ -1,11 +1,11 @@
/*!
-lindsvg v1.4.0
+lindsvg v1.5.0
https://amphiluke.github.io/lindsvg/
(c) 2024 Amphiluke
*/
let messages = {
- AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,[,]",
- RULE: "Production rules may only contain the following characters: A..Z,+,-,|,[,]",
+ AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",
+ RULE: "Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",
LETTER: "Allowed alphabet letters are: A..Z",
ALPHA: "The “alpha” parameter must be a finite number",
THETA: "The “theta” parameter must be a finite number",
@@ -19,7 +19,7 @@ function checkLetter(letter, msg = messages.LETTER) {
return letterRE.test(letter) || msg;
}
-let ruleRE = /^[A-Z+\-[\]|]*$/;
+let ruleRE = /^[A-Z+\-|![\]]*$/;
function checkRule(rule, msg = messages.RULE) {
return ruleRE.test(rule) || msg;
}
@@ -116,6 +116,7 @@ let ctrlRules = {
"+": "+",
"-": "-",
"|": "|",
+ "!": "!",
"[": "[",
"]": "]",
};
@@ -135,7 +136,7 @@ let defaults = {
*/
function cleanCodeword(codeword) {
// Remove auxiliary drawing-indifferent letters
- let cleanCodeword = codeword.replace(/[^FB[\]+-|]/g, "");
+ let cleanCodeword = codeword.replace(/[^FB[\]+-|!]/g, "");
do {
codeword = cleanCodeword;
// Remove useless brackets that don’t contain F commands or other brackets (preserving bracket balance!)
@@ -168,7 +169,7 @@ function generateCodeword(lsParams) {
* @return {String[]}
*/
function tokenizeCodeword(codeword) {
- return codeword.match(/([FB[\]+-|])\1*/g); // tokenize
+ return codeword.match(/([FB[\]+-|!])\1*/g); // tokenize
}
class Turtle {
@@ -198,6 +199,10 @@ class Turtle {
this.alpha += (repeatCount % 2) * Math.PI;
}
+ swapSigns(repeatCount = 1) {
+ this.theta *= (-1) ** repeatCount;
+ }
+
pushStack(repeatCount = 1) {
for (; repeatCount > 0; repeatCount--) {
this.stack.push({x: this.x, y: this.y, alpha: this.alpha});
@@ -269,6 +274,9 @@ function getPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
turtle.pushStack(tokenLength);
break;
@@ -321,6 +329,9 @@ function getMultiPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
branchLevel += tokenLength;
turtle.pushStack(tokenLength);
diff --git a/dist/lindsvg.esm.min.js b/dist/lindsvg.esm.min.js
index 6cfaa23..0635445 100644
--- a/dist/lindsvg.esm.min.js
+++ b/dist/lindsvg.esm.min.js
@@ -1,6 +1,6 @@
/*!
-lindsvg v1.4.0
+lindsvg v1.5.0
https://amphiluke.github.io/lindsvg/
(c) 2024 Amphiluke
*/
-let t={AXIOM:"Axiom may only contain the following characters: A..Z,+,-,|,[,]",RULE:"Production rules may only contain the following characters: A..Z,+,-,|,[,]",LETTER:"Allowed alphabet letters are: A..Z",ALPHA:"The “alpha” parameter must be a finite number",THETA:"The “theta” parameter must be a finite number",STEP:"The “step” parameter must be a positive finite number",COUNT:"The number of iterations must be integer and finite",NUMBER:"A valid finite number expected"},e=/^[A-Z]$/;let a=/^[A-Z+\-[\]|]*$/;function i(e,i=t.RULE){return a.test(e)||i}function r(a,r,n){let s=Object.create(null);return Object.entries(a).forEach((([a,h])=>{let c=function(a,i=t.LETTER){return e.test(a)||i}(a,r);!0===c&&(c=i(h,n)),!0!==c&&(s[a]=c)})),!Object.keys(s).length||s}function n(e){let a=Object.create(null);return Object.entries(e).forEach((([e,n])=>{let s=!0;switch(e){case"axiom":s=i(n,t.AXIOM);break;case"rules":s=r(n);break;case"alpha":case"theta":s=function(e,a=t.NUMBER){return Number.isFinite(e)||a}(n,t[e.toUpperCase()]);break;case"step":s=function(e,a=t.STEP){return Number.isFinite(e)&&e>0||a}(n);break;case"iterations":s=function(e,a=t.COUNT){return Number.isInteger(e)&&e>0||a}(n)}!0!==s&&(a[e]=s)})),!Object.keys(a).length||a}class s extends Error{constructor(t){let e=JSON.stringify(t,null,2);super(e),Object.defineProperty(this,"lsErrors",{value:JSON.parse(e)})}toJSON(){return JSON.parse(JSON.stringify(this.lsErrors))}}Object.defineProperty(s.prototype,"name",{configurable:!0,enumerable:!1,writable:!0,value:"LSError"});let h={F:"",B:"","+":"+","-":"-","|":"|","[":"[","]":"]"},c={alpha:0,theta:0,step:10,iterations:3};function o(t){let e=n(t);if(!0!==e)throw new s(e);let{axiom:a,iterations:i}={...c,...t},r={...h,...t.rules};for(;i>0;i--)a=[...a].reduce(((t,e)=>t+(r[e]||"")),"");return function(t){let e=t.replace(/[^FB[\]+-|]/g,"");do{t=e,e=e.replace(/\[[^F[\]]*]/g,"")}while(e!==t);return e}(a)}function l(t){return t.match(/([FB[\]+-|])\1*/g)}class u{constructor({x:t,y:e,step:a,alpha:i,theta:r}){this.stack=[],this.x=this.minX=this.maxX=t,this.y=this.minY=this.maxY=e,this.step=a,this.alpha=-i,this.theta=r}translate(t=1){this.x+=t*this.step*Math.cos(this.alpha),this.y+=t*this.step*Math.sin(this.alpha),this.minX=Math.min(this.minX,this.x),this.maxX=Math.max(this.maxX,this.x),this.minY=Math.min(this.minY,this.y),this.maxY=Math.max(this.maxY,this.y)}rotate(t){this.alpha+=t*this.theta}reverse(t=1){this.alpha+=t%2*Math.PI}pushStack(t=1){for(;t>0;t--)this.stack.push({x:this.x,y:this.y,alpha:this.alpha})}popStack(t){for(;t>0;t--)({x:this.x,y:this.y,alpha:this.alpha}=this.stack.pop())}getDrawingRect(){let t=Math.floor(this.minX),e=Math.floor(this.minY),a=Math.ceil(this.maxX),i=Math.ceil(this.maxY);return{minX:t,minY:e,maxX:a,maxY:i,width:a-t,height:i-e}}}function p(t,e){return`${+t.toFixed(4)} ${+e.toFixed(4)}`}function m(t){return t.replace(/(?:M-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?)+(?=M|$)/g,"")}function f(t){let e=o(t),a=new u({x:0,y:0,...t}),i=function(t,e){let a;return m(t.reduce(((t,i)=>{let r=i.length;switch(i[0]){case"F":e.translate(r),t+=("L"===a?" ":"L")+p(e.x,e.y),a="L";break;case"B":e.translate(r),"M"===a&&(t=t.slice(0,t.lastIndexOf("M"))),t+="M"+p(e.x,e.y),a="M";break;case"+":e.rotate(r);break;case"-":e.rotate(-r);break;case"|":e.reverse(r);break;case"[":e.pushStack(r);break;case"]":e.popStack(r),t+=`M${p(e.x,e.y)}`,a="M"}return t}),"M"+p(e.x,e.y)))}(l(e),a);return{pathData:i,...a.getDrawingRect()}}function x(t){let e=o(t),a=new u({x:0,y:0,...t}),i=function(t,e){let a,i=0;return t.reduce(((t,r)=>{let n=t[i]||"",s=r.length;switch(r[0]){case"F":e.translate(s),n+=("L"===a?" ":"L")+p(e.x,e.y),a="L";break;case"B":e.translate(s),"M"===a&&(n=n.slice(0,n.lastIndexOf("M"))),n+="M"+p(e.x,e.y),a="M";break;case"+":e.rotate(s);break;case"-":e.rotate(-s);break;case"|":e.reverse(s);break;case"[":i+=s,e.pushStack(s),n=`${t[i]||""}M${p(e.x,e.y)}`,a="M";break;case"]":i-=s,e.popStack(s),n=`${t[i]||""}M${p(e.x,e.y)}`,a="M"}return t[i]=n,t}),["M"+p(e.x,e.y)]).filter((t=>t.includes("L"))).map(m)}(l(e),a);return{multiPathData:i,...a.getDrawingRect()}}function b(t,e,a){return{width:t.width||e,height:t.height||a,padding:t.padding||0,pathAttributes:{fill:t.fill||"none",stroke:t.stroke||"#000",...t.pathAttributes}}}function d({viewBox:t,width:e,height:a,content:i}){return``}function g(t,e){return Object.entries(t).reduce(((t,[a,i])=>(Array.isArray(i)&&(i=i[Math.min(e,i.length-1)]),void 0===i||"n/a"===i.toLowerCase()?t:`${t} ${a}="${i=i.replace(/"/g,""")}"`)),"")}function w(t,e){let{pathData:a,minX:i,minY:r,width:n,height:s}=f(t),{padding:h,width:c,height:o,pathAttributes:l}=b(e,n,s);return d({viewBox:[i-h,r-h,n+2*h,s+2*h],width:c,height:o,content:``})}function M(t,e){let{multiPathData:a,minX:i,minY:r,width:n,height:s}=x(t),{padding:h,width:c,height:o,pathAttributes:l}=b(e,n,s);return d({viewBox:[i-h,r-h,n+2*h,s+2*h],width:c,height:o,content:a.reduce(((t,e,a)=>`${t}`),"")})}export{M as getMultiPathSVGCode,x as getMultiPathSVGData,w as getSVGCode,f as getSVGData};
+let t={AXIOM:"Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",RULE:"Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",LETTER:"Allowed alphabet letters are: A..Z",ALPHA:"The “alpha” parameter must be a finite number",THETA:"The “theta” parameter must be a finite number",STEP:"The “step” parameter must be a positive finite number",COUNT:"The number of iterations must be integer and finite",NUMBER:"A valid finite number expected"},e=/^[A-Z]$/;let a=/^[A-Z+\-|![\]]*$/;function i(e,i=t.RULE){return a.test(e)||i}function r(a,r,n){let s=Object.create(null);return Object.entries(a).forEach((([a,h])=>{let c=function(a,i=t.LETTER){return e.test(a)||i}(a,r);!0===c&&(c=i(h,n)),!0!==c&&(s[a]=c)})),!Object.keys(s).length||s}function n(e){let a=Object.create(null);return Object.entries(e).forEach((([e,n])=>{let s=!0;switch(e){case"axiom":s=i(n,t.AXIOM);break;case"rules":s=r(n);break;case"alpha":case"theta":s=function(e,a=t.NUMBER){return Number.isFinite(e)||a}(n,t[e.toUpperCase()]);break;case"step":s=function(e,a=t.STEP){return Number.isFinite(e)&&e>0||a}(n);break;case"iterations":s=function(e,a=t.COUNT){return Number.isInteger(e)&&e>0||a}(n)}!0!==s&&(a[e]=s)})),!Object.keys(a).length||a}class s extends Error{constructor(t){let e=JSON.stringify(t,null,2);super(e),Object.defineProperty(this,"lsErrors",{value:JSON.parse(e)})}toJSON(){return JSON.parse(JSON.stringify(this.lsErrors))}}Object.defineProperty(s.prototype,"name",{configurable:!0,enumerable:!1,writable:!0,value:"LSError"});let h={F:"",B:"","+":"+","-":"-","|":"|","!":"!","[":"[","]":"]"},c={alpha:0,theta:0,step:10,iterations:3};function o(t){let e=n(t);if(!0!==e)throw new s(e);let{axiom:a,iterations:i}={...c,...t},r={...h,...t.rules};for(;i>0;i--)a=[...a].reduce(((t,e)=>t+(r[e]||"")),"");return function(t){let e=t.replace(/[^FB[\]+-|!]/g,"");do{t=e,e=e.replace(/\[[^F[\]]*]/g,"")}while(e!==t);return e}(a)}function l(t){return t.match(/([FB[\]+-|!])\1*/g)}class u{constructor({x:t,y:e,step:a,alpha:i,theta:r}){this.stack=[],this.x=this.minX=this.maxX=t,this.y=this.minY=this.maxY=e,this.step=a,this.alpha=-i,this.theta=r}translate(t=1){this.x+=t*this.step*Math.cos(this.alpha),this.y+=t*this.step*Math.sin(this.alpha),this.minX=Math.min(this.minX,this.x),this.maxX=Math.max(this.maxX,this.x),this.minY=Math.min(this.minY,this.y),this.maxY=Math.max(this.maxY,this.y)}rotate(t){this.alpha+=t*this.theta}reverse(t=1){this.alpha+=t%2*Math.PI}swapSigns(t=1){this.theta*=(-1)**t}pushStack(t=1){for(;t>0;t--)this.stack.push({x:this.x,y:this.y,alpha:this.alpha})}popStack(t){for(;t>0;t--)({x:this.x,y:this.y,alpha:this.alpha}=this.stack.pop())}getDrawingRect(){let t=Math.floor(this.minX),e=Math.floor(this.minY),a=Math.ceil(this.maxX),i=Math.ceil(this.maxY);return{minX:t,minY:e,maxX:a,maxY:i,width:a-t,height:i-e}}}function p(t,e){return`${+t.toFixed(4)} ${+e.toFixed(4)}`}function m(t){return t.replace(/(?:M-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?)+(?=M|$)/g,"")}function b(t){let e=o(t),a=new u({x:0,y:0,...t}),i=function(t,e){let a;return m(t.reduce(((t,i)=>{let r=i.length;switch(i[0]){case"F":e.translate(r),t+=("L"===a?" ":"L")+p(e.x,e.y),a="L";break;case"B":e.translate(r),"M"===a&&(t=t.slice(0,t.lastIndexOf("M"))),t+="M"+p(e.x,e.y),a="M";break;case"+":e.rotate(r);break;case"-":e.rotate(-r);break;case"|":e.reverse(r);break;case"!":e.swapSigns(r);break;case"[":e.pushStack(r);break;case"]":e.popStack(r),t+=`M${p(e.x,e.y)}`,a="M"}return t}),"M"+p(e.x,e.y)))}(l(e),a);return{pathData:i,...a.getDrawingRect()}}function f(t){let e=o(t),a=new u({x:0,y:0,...t}),i=function(t,e){let a,i=0;return t.reduce(((t,r)=>{let n=t[i]||"",s=r.length;switch(r[0]){case"F":e.translate(s),n+=("L"===a?" ":"L")+p(e.x,e.y),a="L";break;case"B":e.translate(s),"M"===a&&(n=n.slice(0,n.lastIndexOf("M"))),n+="M"+p(e.x,e.y),a="M";break;case"+":e.rotate(s);break;case"-":e.rotate(-s);break;case"|":e.reverse(s);break;case"!":e.swapSigns(s);break;case"[":i+=s,e.pushStack(s),n=`${t[i]||""}M${p(e.x,e.y)}`,a="M";break;case"]":i-=s,e.popStack(s),n=`${t[i]||""}M${p(e.x,e.y)}`,a="M"}return t[i]=n,t}),["M"+p(e.x,e.y)]).filter((t=>t.includes("L"))).map(m)}(l(e),a);return{multiPathData:i,...a.getDrawingRect()}}function x(t,e,a){return{width:t.width||e,height:t.height||a,padding:t.padding||0,pathAttributes:{fill:t.fill||"none",stroke:t.stroke||"#000",...t.pathAttributes}}}function d({viewBox:t,width:e,height:a,content:i}){return``}function g(t,e){return Object.entries(t).reduce(((t,[a,i])=>(Array.isArray(i)&&(i=i[Math.min(e,i.length-1)]),void 0===i||"n/a"===i.toLowerCase()?t:`${t} ${a}="${i=i.replace(/"/g,""")}"`)),"")}function w(t,e){let{pathData:a,minX:i,minY:r,width:n,height:s}=b(t),{padding:h,width:c,height:o,pathAttributes:l}=x(e,n,s);return d({viewBox:[i-h,r-h,n+2*h,s+2*h],width:c,height:o,content:``})}function M(t,e){let{multiPathData:a,minX:i,minY:r,width:n,height:s}=f(t),{padding:h,width:c,height:o,pathAttributes:l}=x(e,n,s);return d({viewBox:[i-h,r-h,n+2*h,s+2*h],width:c,height:o,content:a.reduce(((t,e,a)=>`${t}`),"")})}export{M as getMultiPathSVGCode,f as getMultiPathSVGData,w as getSVGCode,b as getSVGData};
diff --git a/dist/lindsvg.js b/dist/lindsvg.js
index 4d58265..905d63a 100644
--- a/dist/lindsvg.js
+++ b/dist/lindsvg.js
@@ -1,5 +1,5 @@
/*!
-lindsvg v1.4.0
+lindsvg v1.5.0
https://amphiluke.github.io/lindsvg/
(c) 2024 Amphiluke
*/
@@ -10,8 +10,8 @@ https://amphiluke.github.io/lindsvg/
})(this, (function (exports) { 'use strict';
let messages = {
- AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,[,]",
- RULE: "Production rules may only contain the following characters: A..Z,+,-,|,[,]",
+ AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",
+ RULE: "Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",
LETTER: "Allowed alphabet letters are: A..Z",
ALPHA: "The “alpha” parameter must be a finite number",
THETA: "The “theta” parameter must be a finite number",
@@ -25,7 +25,7 @@ https://amphiluke.github.io/lindsvg/
return letterRE.test(letter) || msg;
}
- let ruleRE = /^[A-Z+\-[\]|]*$/;
+ let ruleRE = /^[A-Z+\-|![\]]*$/;
function checkRule(rule, msg = messages.RULE) {
return ruleRE.test(rule) || msg;
}
@@ -122,6 +122,7 @@ https://amphiluke.github.io/lindsvg/
"+": "+",
"-": "-",
"|": "|",
+ "!": "!",
"[": "[",
"]": "]",
};
@@ -141,7 +142,7 @@ https://amphiluke.github.io/lindsvg/
*/
function cleanCodeword(codeword) {
// Remove auxiliary drawing-indifferent letters
- let cleanCodeword = codeword.replace(/[^FB[\]+-|]/g, "");
+ let cleanCodeword = codeword.replace(/[^FB[\]+-|!]/g, "");
do {
codeword = cleanCodeword;
// Remove useless brackets that don’t contain F commands or other brackets (preserving bracket balance!)
@@ -174,7 +175,7 @@ https://amphiluke.github.io/lindsvg/
* @return {String[]}
*/
function tokenizeCodeword(codeword) {
- return codeword.match(/([FB[\]+-|])\1*/g); // tokenize
+ return codeword.match(/([FB[\]+-|!])\1*/g); // tokenize
}
class Turtle {
@@ -204,6 +205,10 @@ https://amphiluke.github.io/lindsvg/
this.alpha += (repeatCount % 2) * Math.PI;
}
+ swapSigns(repeatCount = 1) {
+ this.theta *= (-1) ** repeatCount;
+ }
+
pushStack(repeatCount = 1) {
for (; repeatCount > 0; repeatCount--) {
this.stack.push({x: this.x, y: this.y, alpha: this.alpha});
@@ -275,6 +280,9 @@ https://amphiluke.github.io/lindsvg/
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
turtle.pushStack(tokenLength);
break;
@@ -327,6 +335,9 @@ https://amphiluke.github.io/lindsvg/
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
branchLevel += tokenLength;
turtle.pushStack(tokenLength);
diff --git a/dist/lindsvg.min.js b/dist/lindsvg.min.js
index 3907623..2ff6b1a 100644
--- a/dist/lindsvg.min.js
+++ b/dist/lindsvg.min.js
@@ -1,6 +1,6 @@
/*!
-lindsvg v1.4.0
+lindsvg v1.5.0
https://amphiluke.github.io/lindsvg/
(c) 2024 Amphiluke
*/
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).lindsvg={})}(this,(function(t){"use strict";let e={AXIOM:"Axiom may only contain the following characters: A..Z,+,-,|,[,]",RULE:"Production rules may only contain the following characters: A..Z,+,-,|,[,]",LETTER:"Allowed alphabet letters are: A..Z",ALPHA:"The “alpha” parameter must be a finite number",THETA:"The “theta” parameter must be a finite number",STEP:"The “step” parameter must be a positive finite number",COUNT:"The number of iterations must be integer and finite",NUMBER:"A valid finite number expected"},a=/^[A-Z]$/;let i=/^[A-Z+\-[\]|]*$/;function r(t,a=e.RULE){return i.test(t)||a}function n(t,i,n){let s=Object.create(null);return Object.entries(t).forEach((([t,h])=>{let o=function(t,i=e.LETTER){return a.test(t)||i}(t,i);!0===o&&(o=r(h,n)),!0!==o&&(s[t]=o)})),!Object.keys(s).length||s}function s(t){let a=Object.create(null);return Object.entries(t).forEach((([t,i])=>{let s=!0;switch(t){case"axiom":s=r(i,e.AXIOM);break;case"rules":s=n(i);break;case"alpha":case"theta":s=function(t,a=e.NUMBER){return Number.isFinite(t)||a}(i,e[t.toUpperCase()]);break;case"step":s=function(t,a=e.STEP){return Number.isFinite(t)&&t>0||a}(i);break;case"iterations":s=function(t,a=e.COUNT){return Number.isInteger(t)&&t>0||a}(i)}!0!==s&&(a[t]=s)})),!Object.keys(a).length||a}class h extends Error{constructor(t){let e=JSON.stringify(t,null,2);super(e),Object.defineProperty(this,"lsErrors",{value:JSON.parse(e)})}toJSON(){return JSON.parse(JSON.stringify(this.lsErrors))}}Object.defineProperty(h.prototype,"name",{configurable:!0,enumerable:!1,writable:!0,value:"LSError"});let o={F:"",B:"","+":"+","-":"-","|":"|","[":"[","]":"]"},c={alpha:0,theta:0,step:10,iterations:3};function l(t){let e=s(t);if(!0!==e)throw new h(e);let{axiom:a,iterations:i}={...c,...t},r={...o,...t.rules};for(;i>0;i--)a=[...a].reduce(((t,e)=>t+(r[e]||"")),"");return function(t){let e=t.replace(/[^FB[\]+-|]/g,"");do{t=e,e=e.replace(/\[[^F[\]]*]/g,"")}while(e!==t);return e}(a)}function u(t){return t.match(/([FB[\]+-|])\1*/g)}class p{constructor({x:t,y:e,step:a,alpha:i,theta:r}){this.stack=[],this.x=this.minX=this.maxX=t,this.y=this.minY=this.maxY=e,this.step=a,this.alpha=-i,this.theta=r}translate(t=1){this.x+=t*this.step*Math.cos(this.alpha),this.y+=t*this.step*Math.sin(this.alpha),this.minX=Math.min(this.minX,this.x),this.maxX=Math.max(this.maxX,this.x),this.minY=Math.min(this.minY,this.y),this.maxY=Math.max(this.maxY,this.y)}rotate(t){this.alpha+=t*this.theta}reverse(t=1){this.alpha+=t%2*Math.PI}pushStack(t=1){for(;t>0;t--)this.stack.push({x:this.x,y:this.y,alpha:this.alpha})}popStack(t){for(;t>0;t--)({x:this.x,y:this.y,alpha:this.alpha}=this.stack.pop())}getDrawingRect(){let t=Math.floor(this.minX),e=Math.floor(this.minY),a=Math.ceil(this.maxX),i=Math.ceil(this.maxY);return{minX:t,minY:e,maxX:a,maxY:i,width:a-t,height:i-e}}}function f(t,e){return`${+t.toFixed(4)} ${+e.toFixed(4)}`}function m(t){return t.replace(/(?:M-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?)+(?=M|$)/g,"")}function d(t){let e=l(t),a=new p({x:0,y:0,...t}),i=function(t,e){let a;return m(t.reduce(((t,i)=>{let r=i.length;switch(i[0]){case"F":e.translate(r),t+=("L"===a?" ":"L")+f(e.x,e.y),a="L";break;case"B":e.translate(r),"M"===a&&(t=t.slice(0,t.lastIndexOf("M"))),t+="M"+f(e.x,e.y),a="M";break;case"+":e.rotate(r);break;case"-":e.rotate(-r);break;case"|":e.reverse(r);break;case"[":e.pushStack(r);break;case"]":e.popStack(r),t+=`M${f(e.x,e.y)}`,a="M"}return t}),"M"+f(e.x,e.y)))}(u(e),a);return{pathData:i,...a.getDrawingRect()}}function g(t){let e=l(t),a=new p({x:0,y:0,...t}),i=function(t,e){let a,i=0;return t.reduce(((t,r)=>{let n=t[i]||"",s=r.length;switch(r[0]){case"F":e.translate(s),n+=("L"===a?" ":"L")+f(e.x,e.y),a="L";break;case"B":e.translate(s),"M"===a&&(n=n.slice(0,n.lastIndexOf("M"))),n+="M"+f(e.x,e.y),a="M";break;case"+":e.rotate(s);break;case"-":e.rotate(-s);break;case"|":e.reverse(s);break;case"[":i+=s,e.pushStack(s),n=`${t[i]||""}M${f(e.x,e.y)}`,a="M";break;case"]":i-=s,e.popStack(s),n=`${t[i]||""}M${f(e.x,e.y)}`,a="M"}return t[i]=n,t}),["M"+f(e.x,e.y)]).filter((t=>t.includes("L"))).map(m)}(u(e),a);return{multiPathData:i,...a.getDrawingRect()}}function x(t,e,a){return{width:t.width||e,height:t.height||a,padding:t.padding||0,pathAttributes:{fill:t.fill||"none",stroke:t.stroke||"#000",...t.pathAttributes}}}function b({viewBox:t,width:e,height:a,content:i}){return``}function y(t,e){return Object.entries(t).reduce(((t,[a,i])=>(Array.isArray(i)&&(i=i[Math.min(e,i.length-1)]),void 0===i||"n/a"===i.toLowerCase()?t:`${t} ${a}="${i=i.replace(/"/g,""")}"`)),"")}t.getMultiPathSVGCode=function(t,e){let{multiPathData:a,minX:i,minY:r,width:n,height:s}=g(t),{padding:h,width:o,height:c,pathAttributes:l}=x(e,n,s);return b({viewBox:[i-h,r-h,n+2*h,s+2*h],width:o,height:c,content:a.reduce(((t,e,a)=>`${t}`),"")})},t.getMultiPathSVGData=g,t.getSVGCode=function(t,e){let{pathData:a,minX:i,minY:r,width:n,height:s}=d(t),{padding:h,width:o,height:c,pathAttributes:l}=x(e,n,s);return b({viewBox:[i-h,r-h,n+2*h,s+2*h],width:o,height:c,content:``})},t.getSVGData=d}));
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).lindsvg={})}(this,(function(t){"use strict";let e={AXIOM:"Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",RULE:"Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",LETTER:"Allowed alphabet letters are: A..Z",ALPHA:"The “alpha” parameter must be a finite number",THETA:"The “theta” parameter must be a finite number",STEP:"The “step” parameter must be a positive finite number",COUNT:"The number of iterations must be integer and finite",NUMBER:"A valid finite number expected"},a=/^[A-Z]$/;let i=/^[A-Z+\-|![\]]*$/;function r(t,a=e.RULE){return i.test(t)||a}function n(t,i,n){let s=Object.create(null);return Object.entries(t).forEach((([t,h])=>{let o=function(t,i=e.LETTER){return a.test(t)||i}(t,i);!0===o&&(o=r(h,n)),!0!==o&&(s[t]=o)})),!Object.keys(s).length||s}function s(t){let a=Object.create(null);return Object.entries(t).forEach((([t,i])=>{let s=!0;switch(t){case"axiom":s=r(i,e.AXIOM);break;case"rules":s=n(i);break;case"alpha":case"theta":s=function(t,a=e.NUMBER){return Number.isFinite(t)||a}(i,e[t.toUpperCase()]);break;case"step":s=function(t,a=e.STEP){return Number.isFinite(t)&&t>0||a}(i);break;case"iterations":s=function(t,a=e.COUNT){return Number.isInteger(t)&&t>0||a}(i)}!0!==s&&(a[t]=s)})),!Object.keys(a).length||a}class h extends Error{constructor(t){let e=JSON.stringify(t,null,2);super(e),Object.defineProperty(this,"lsErrors",{value:JSON.parse(e)})}toJSON(){return JSON.parse(JSON.stringify(this.lsErrors))}}Object.defineProperty(h.prototype,"name",{configurable:!0,enumerable:!1,writable:!0,value:"LSError"});let o={F:"",B:"","+":"+","-":"-","|":"|","!":"!","[":"[","]":"]"},c={alpha:0,theta:0,step:10,iterations:3};function l(t){let e=s(t);if(!0!==e)throw new h(e);let{axiom:a,iterations:i}={...c,...t},r={...o,...t.rules};for(;i>0;i--)a=[...a].reduce(((t,e)=>t+(r[e]||"")),"");return function(t){let e=t.replace(/[^FB[\]+-|!]/g,"");do{t=e,e=e.replace(/\[[^F[\]]*]/g,"")}while(e!==t);return e}(a)}function u(t){return t.match(/([FB[\]+-|!])\1*/g)}class p{constructor({x:t,y:e,step:a,alpha:i,theta:r}){this.stack=[],this.x=this.minX=this.maxX=t,this.y=this.minY=this.maxY=e,this.step=a,this.alpha=-i,this.theta=r}translate(t=1){this.x+=t*this.step*Math.cos(this.alpha),this.y+=t*this.step*Math.sin(this.alpha),this.minX=Math.min(this.minX,this.x),this.maxX=Math.max(this.maxX,this.x),this.minY=Math.min(this.minY,this.y),this.maxY=Math.max(this.maxY,this.y)}rotate(t){this.alpha+=t*this.theta}reverse(t=1){this.alpha+=t%2*Math.PI}swapSigns(t=1){this.theta*=(-1)**t}pushStack(t=1){for(;t>0;t--)this.stack.push({x:this.x,y:this.y,alpha:this.alpha})}popStack(t){for(;t>0;t--)({x:this.x,y:this.y,alpha:this.alpha}=this.stack.pop())}getDrawingRect(){let t=Math.floor(this.minX),e=Math.floor(this.minY),a=Math.ceil(this.maxX),i=Math.ceil(this.maxY);return{minX:t,minY:e,maxX:a,maxY:i,width:a-t,height:i-e}}}function f(t,e){return`${+t.toFixed(4)} ${+e.toFixed(4)}`}function m(t){return t.replace(/(?:M-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?)+(?=M|$)/g,"")}function d(t){let e=l(t),a=new p({x:0,y:0,...t}),i=function(t,e){let a;return m(t.reduce(((t,i)=>{let r=i.length;switch(i[0]){case"F":e.translate(r),t+=("L"===a?" ":"L")+f(e.x,e.y),a="L";break;case"B":e.translate(r),"M"===a&&(t=t.slice(0,t.lastIndexOf("M"))),t+="M"+f(e.x,e.y),a="M";break;case"+":e.rotate(r);break;case"-":e.rotate(-r);break;case"|":e.reverse(r);break;case"!":e.swapSigns(r);break;case"[":e.pushStack(r);break;case"]":e.popStack(r),t+=`M${f(e.x,e.y)}`,a="M"}return t}),"M"+f(e.x,e.y)))}(u(e),a);return{pathData:i,...a.getDrawingRect()}}function g(t){let e=l(t),a=new p({x:0,y:0,...t}),i=function(t,e){let a,i=0;return t.reduce(((t,r)=>{let n=t[i]||"",s=r.length;switch(r[0]){case"F":e.translate(s),n+=("L"===a?" ":"L")+f(e.x,e.y),a="L";break;case"B":e.translate(s),"M"===a&&(n=n.slice(0,n.lastIndexOf("M"))),n+="M"+f(e.x,e.y),a="M";break;case"+":e.rotate(s);break;case"-":e.rotate(-s);break;case"|":e.reverse(s);break;case"!":e.swapSigns(s);break;case"[":i+=s,e.pushStack(s),n=`${t[i]||""}M${f(e.x,e.y)}`,a="M";break;case"]":i-=s,e.popStack(s),n=`${t[i]||""}M${f(e.x,e.y)}`,a="M"}return t[i]=n,t}),["M"+f(e.x,e.y)]).filter((t=>t.includes("L"))).map(m)}(u(e),a);return{multiPathData:i,...a.getDrawingRect()}}function b(t,e,a){return{width:t.width||e,height:t.height||a,padding:t.padding||0,pathAttributes:{fill:t.fill||"none",stroke:t.stroke||"#000",...t.pathAttributes}}}function x({viewBox:t,width:e,height:a,content:i}){return``}function w(t,e){return Object.entries(t).reduce(((t,[a,i])=>(Array.isArray(i)&&(i=i[Math.min(e,i.length-1)]),void 0===i||"n/a"===i.toLowerCase()?t:`${t} ${a}="${i=i.replace(/"/g,""")}"`)),"")}t.getMultiPathSVGCode=function(t,e){let{multiPathData:a,minX:i,minY:r,width:n,height:s}=g(t),{padding:h,width:o,height:c,pathAttributes:l}=b(e,n,s);return x({viewBox:[i-h,r-h,n+2*h,s+2*h],width:o,height:c,content:a.reduce(((t,e,a)=>`${t}`),"")})},t.getMultiPathSVGData=g,t.getSVGCode=function(t,e){let{pathData:a,minX:i,minY:r,width:n,height:s}=d(t),{padding:h,width:o,height:c,pathAttributes:l}=b(e,n,s);return x({viewBox:[i-h,r-h,n+2*h,s+2*h],width:o,height:c,content:``})},t.getSVGData=d}));
diff --git a/package-lock.json b/package-lock.json
index 8367301..159861f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "lindsvg",
- "version": "1.4.0",
+ "version": "1.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lindsvg",
- "version": "1.4.0",
+ "version": "1.5.0",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
diff --git a/package.json b/package.json
index 090c88a..460ac0f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lindsvg",
- "version": "1.4.0",
+ "version": "1.5.0",
"description": "Lindenmayer System [Scalable] Vector Graphics",
"main": "./dist/lindsvg.js",
"module": "./dist/lindsvg.esm.js",
diff --git a/src/generator.mjs b/src/generator.mjs
index cb92f10..d0b1e2b 100644
--- a/src/generator.mjs
+++ b/src/generator.mjs
@@ -8,6 +8,7 @@ let ctrlRules = {
"+": "+",
"-": "-",
"|": "|",
+ "!": "!",
"[": "[",
"]": "]",
};
@@ -27,7 +28,7 @@ let defaults = {
*/
function cleanCodeword(codeword) {
// Remove auxiliary drawing-indifferent letters
- let cleanCodeword = codeword.replace(/[^FB[\]+-|]/g, "");
+ let cleanCodeword = codeword.replace(/[^FB[\]+-|!]/g, "");
do {
codeword = cleanCodeword;
// Remove useless brackets that don’t contain F commands or other brackets (preserving bracket balance!)
@@ -60,5 +61,5 @@ export function generateCodeword(lsParams) {
* @return {String[]}
*/
export function tokenizeCodeword(codeword) {
- return codeword.match(/([FB[\]+-|])\1*/g); // tokenize
+ return codeword.match(/([FB[\]+-|!])\1*/g); // tokenize
}
diff --git a/src/svg.mjs b/src/svg.mjs
index 9980ed6..7fb0120 100644
--- a/src/svg.mjs
+++ b/src/svg.mjs
@@ -51,6 +51,9 @@ function getPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
turtle.pushStack(tokenLength);
break;
@@ -103,6 +106,9 @@ function getMultiPathData(tokens, turtle) {
case "|":
turtle.reverse(tokenLength);
break;
+ case "!":
+ turtle.swapSigns(tokenLength);
+ break;
case "[":
branchLevel += tokenLength;
turtle.pushStack(tokenLength);
diff --git a/src/turtle.mjs b/src/turtle.mjs
index e6b7f26..f58bd3a 100644
--- a/src/turtle.mjs
+++ b/src/turtle.mjs
@@ -25,6 +25,10 @@ export class Turtle {
this.alpha += (repeatCount % 2) * Math.PI;
}
+ swapSigns(repeatCount = 1) {
+ this.theta *= (-1) ** repeatCount;
+ }
+
pushStack(repeatCount = 1) {
for (; repeatCount > 0; repeatCount--) {
this.stack.push({x: this.x, y: this.y, alpha: this.alpha});
diff --git a/src/validator.mjs b/src/validator.mjs
index 8efaec5..c3a821f 100644
--- a/src/validator.mjs
+++ b/src/validator.mjs
@@ -1,6 +1,6 @@
let messages = {
- AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,[,]",
- RULE: "Production rules may only contain the following characters: A..Z,+,-,|,[,]",
+ AXIOM: "Axiom may only contain the following characters: A..Z,+,-,|,!,[,]",
+ RULE: "Production rules may only contain the following characters: A..Z,+,-,|,!,[,]",
LETTER: "Allowed alphabet letters are: A..Z",
ALPHA: "The “alpha” parameter must be a finite number",
THETA: "The “theta” parameter must be a finite number",
@@ -14,7 +14,7 @@ export function checkLetter(letter, msg = messages.LETTER) {
return letterRE.test(letter) || msg;
}
-let ruleRE = /^[A-Z+\-[\]|]*$/;
+let ruleRE = /^[A-Z+\-|![\]]*$/;
export function checkRule(rule, msg = messages.RULE) {
return ruleRE.test(rule) || msg;
}
diff --git a/test/install-test/package-lock.json b/test/install-test/package-lock.json
index e664e24..4f69de2 100644
--- a/test/install-test/package-lock.json
+++ b/test/install-test/package-lock.json
@@ -9,13 +9,13 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
- "lindsvg": "file:../../lindsvg-1.4.0.tgz"
+ "lindsvg": "file:../../lindsvg-1.5.0.tgz"
}
},
"node_modules/lindsvg": {
- "version": "1.4.0",
- "resolved": "file:../../lindsvg-1.4.0.tgz",
- "integrity": "sha512-yGtZ+UvdchTFbhDVUeNKDZP6cQ6SCos+3RjjY+a/ZabrbiU3pHAJ2mrxJ6+rj8guveh+/O42TMNpTTBKg3lILg==",
+ "version": "1.5.0",
+ "resolved": "file:../../lindsvg-1.5.0.tgz",
+ "integrity": "sha512-JbB9J43ON4CtiVw0axPnl6SLEX585iZ2jPTzH7LzlJVaID5RHA1pPTREUy42Q3ToW0i2ZtWaYwScR0p3frsRkg==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
diff --git a/test/install-test/package.json b/test/install-test/package.json
index 016fd24..04eb55a 100644
--- a/test/install-test/package.json
+++ b/test/install-test/package.json
@@ -9,6 +9,6 @@
"author": "Amphiluke",
"license": "MIT",
"dependencies": {
- "lindsvg": "file:../../lindsvg-1.4.0.tgz"
+ "lindsvg": "file:../../lindsvg-1.5.0.tgz"
}
}
diff --git a/test/params.mjs b/test/params.mjs
index 225c5e5..b8e0b5c 100644
--- a/test/params.mjs
+++ b/test/params.mjs
@@ -11,7 +11,7 @@ export let singlePathLSParams = {
};
export let multiPathLSParams = {
- axiom: "FFF+FFFF-FF+FF-[-Y][+Y][Z][+Z]",
+ axiom: "FFF+FFFF-FF+FF-[-Y][+Y][!Z!][+Z]",
rules: {
F: "F",
Y: "FF+F-F-F[FFFZ][+Z]-F-FZ",