From a6b5a58f6a919046e4ca2edb49e057026ec4bd8c Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 3 Nov 2024 13:10:38 +0100 Subject: [PATCH 1/5] Experimental bitfield feature (warning - has security implications!) --- src/app/core/operations/bitfield/bitfield.ts | 65 ++++++++++++++++++++ src/app/core/provider/operation.service.ts | 2 + src/assets/json/op_classifications.json | 3 +- src/assets/json/op_descriptions.json | 12 ++-- 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/app/core/operations/bitfield/bitfield.ts diff --git a/src/app/core/operations/bitfield/bitfield.ts b/src/app/core/operations/bitfield/bitfield.ts new file mode 100644 index 000000000..2cd399f54 --- /dev/null +++ b/src/app/core/operations/bitfield/bitfield.ts @@ -0,0 +1,65 @@ +import { NumParam, StringParam, OpParamVal, OpInput, Operation } from "../../model/datatypes"; +import { getOpParamValById } from "../../model/operations"; +import { Sequence } from "../../model/sequence"; +import { initDraftFromDrawdown } from "../../model/drafts"; + + +const name = "bitfield"; +const old_names = []; + +//PARAMS +const warps:NumParam = + {name: 'num warps', + type: 'number', + min: 1, + max: 128, + value: 32, + dx: "Number of warps" +}; + +const wefts:NumParam = + {name: 'num warps', + type: 'number', + min: 1, + max: 128, + value: 32, + dx: "Number of wefts" +}; + +const f:StringParam = + {name: 'bitfield function', + type: 'string', + regex: /.*/, + error: 'invalid entry', + value: "(x, y) => (x ^ y) % 3", + dx: "JavaScript function with x/y arguments that returns a boolean value" +}; + +const params = [warps, wefts, f]; + +const inlets = []; + +const perform = (param_vals: Array, op_inputs: Array) => { + const num_warps: number = getOpParamValById(0, param_vals); + const num_wefts: number = getOpParamValById(1, param_vals); + const script: string = getOpParamValById(2, param_vals); + + let func = eval(script); + let pattern = new Sequence.TwoD(); + for (let weft = 0; weft < num_wefts; ++weft) { + const row = new Sequence.OneD(); + for (let warp = 0; warp < num_warps; ++warp) { + row.push(!! func(warp, weft)); + } + pattern.pushWeftSequence(row.val()); + } + + return Promise.resolve([initDraftFromDrawdown(pattern.export())]); +} + +const generateName = (param_vals: Array, op_inputs: Array) : string => { + const num_up: number = getOpParamValById(0, param_vals); + return num_up + '/bitfield'; +} + +export const bitfield: Operation = {name, old_names, params, inlets, perform, generateName}; \ No newline at end of file diff --git a/src/app/core/provider/operation.service.ts b/src/app/core/provider/operation.service.ts index d8b38aac3..781717219 100644 --- a/src/app/core/provider/operation.service.ts +++ b/src/app/core/provider/operation.service.ts @@ -74,6 +74,7 @@ import {flip} from '../operations/flip/flip' import { glitchsatin } from '../operations/glitchsatin/glitchsatin'; import { apply_warp_mats } from '../operations/applywarpmaterials/applywarpmaterials'; import { apply_weft_mats } from '../operations/applyweftmaterials/applyweftmaterials'; +import {bitfield} from '../operations/bitfield/bitfield'; @Injectable({ providedIn: 'root' @@ -174,6 +175,7 @@ export class OperationService { this.ops.push(sawtooth); this.ops.push(glitchsatin) // this.ops.push(hydra) + this.ops.push(bitfield); } diff --git a/src/assets/json/op_classifications.json b/src/assets/json/op_classifications.json index d5008c85f..8bee73e26 100644 --- a/src/assets/json/op_classifications.json +++ b/src/assets/json/op_classifications.json @@ -19,7 +19,8 @@ "sine", "sawtooth", "satinish", - "glitchsatin" + "glitchsatin", + "bitfield" ] }, diff --git a/src/assets/json/op_descriptions.json b/src/assets/json/op_descriptions.json index 1784060dd..43e5a1f16 100644 --- a/src/assets/json/op_descriptions.json +++ b/src/assets/json/op_descriptions.json @@ -525,10 +525,14 @@ "tags": ["advanced"], "description": "Create a drawdown from the input drafts (order 1. threading, 2. tieup, 3.lift plan)", "application": "To create drawdown from from a direct loom plan" - } - - - + }, + { + "name":"bitfield", + "tags": ["advanced"], + "displayname": "bitfield", + "description": "Takes a function from x and y values to boolean result, and applies it to each x/y position to generate a draft.", + "application": "For 'bitfield'-style generation of drafts that look like alien art, inspired by a post by Martin Kleppe on what used to be known as twitter." + } ], "param": [ From 28c9cd2a29c98b107de3cbbd181c09aebe81e32b Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 3 Nov 2024 17:24:58 +0100 Subject: [PATCH 2/5] Limit characters used in bitfields - should be safe-ish now --- src/app/core/operations/bitfield/bitfield.ts | 29 +++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/app/core/operations/bitfield/bitfield.ts b/src/app/core/operations/bitfield/bitfield.ts index 2cd399f54..724674e70 100644 --- a/src/app/core/operations/bitfield/bitfield.ts +++ b/src/app/core/operations/bitfield/bitfield.ts @@ -29,10 +29,10 @@ const wefts:NumParam = const f:StringParam = {name: 'bitfield function', type: 'string', - regex: /.*/, - error: 'invalid entry', - value: "(x, y) => (x ^ y) % 3", - dx: "JavaScript function with x/y arguments that returns a boolean value" + regex: /^[0-9!<>&^\|xy()+-\\ \\%]+$/, + error: 'Invalid - can only contain 0-9, x, y, spaces and the following symbols: <>&^|xy()+-%', + value: "(x ^ y) % 3", + dx: "JavaScript expression that uses x/y values to return a boolean value for each cell" }; const params = [warps, wefts, f]; @@ -44,17 +44,20 @@ const perform = (param_vals: Array, op_inputs: Array) => { const num_wefts: number = getOpParamValById(1, param_vals); const script: string = getOpParamValById(2, param_vals); - let func = eval(script); - let pattern = new Sequence.TwoD(); - for (let weft = 0; weft < num_wefts; ++weft) { - const row = new Sequence.OneD(); - for (let warp = 0; warp < num_warps; ++warp) { - row.push(!! func(warp, weft)); + // This should already have been checked but probably best to check here again in case + // a bad string could have been injected somewhere.. + if (script.match(f.regex)) { + let func = eval('(x, y) => '.concat(script)); + let pattern = new Sequence.TwoD(); + for (let weft = 0; weft < num_wefts; ++weft) { + const row = new Sequence.OneD(); + for (let warp = 0; warp < num_warps; ++warp) { + row.push(!! func(warp, weft)); + } + pattern.pushWeftSequence(row.val()); } - pattern.pushWeftSequence(row.val()); + return Promise.resolve([initDraftFromDrawdown(pattern.export())]); } - - return Promise.resolve([initDraftFromDrawdown(pattern.export())]); } const generateName = (param_vals: Array, op_inputs: Array) : string => { From 009d4f23b82b7a37a630b1098eeb7a7c1e261e34 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 3 Nov 2024 23:29:48 +0100 Subject: [PATCH 3/5] allow * and / in bitfields --- src/app/core/operations/bitfield/bitfield.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/operations/bitfield/bitfield.ts b/src/app/core/operations/bitfield/bitfield.ts index 724674e70..bce39b2bb 100644 --- a/src/app/core/operations/bitfield/bitfield.ts +++ b/src/app/core/operations/bitfield/bitfield.ts @@ -29,7 +29,7 @@ const wefts:NumParam = const f:StringParam = {name: 'bitfield function', type: 'string', - regex: /^[0-9!<>&^\|xy()+-\\ \\%]+$/, + regex: /^[0-9!<>&^\|xy()+-\\*\/\\ \\%]+$/, error: 'Invalid - can only contain 0-9, x, y, spaces and the following symbols: <>&^|xy()+-%', value: "(x ^ y) % 3", dx: "JavaScript expression that uses x/y values to return a boolean value for each cell" From a96a9e6874f97f78bb1df34250ce16cbfe233072 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 4 Nov 2024 22:17:05 +0100 Subject: [PATCH 4/5] Use mathjs for securely parsing/evaluating bitfield expressions --- src/app/core/operations/bitfield/bitfield.ts | 36 +++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/app/core/operations/bitfield/bitfield.ts b/src/app/core/operations/bitfield/bitfield.ts index bce39b2bb..d9fed5e13 100644 --- a/src/app/core/operations/bitfield/bitfield.ts +++ b/src/app/core/operations/bitfield/bitfield.ts @@ -2,7 +2,7 @@ import { NumParam, StringParam, OpParamVal, OpInput, Operation } from "../../mod import { getOpParamValById } from "../../model/operations"; import { Sequence } from "../../model/sequence"; import { initDraftFromDrawdown } from "../../model/drafts"; - +import { evaluate } from 'mathjs'; const name = "bitfield"; const old_names = []; @@ -29,10 +29,10 @@ const wefts:NumParam = const f:StringParam = {name: 'bitfield function', type: 'string', - regex: /^[0-9!<>&^\|xy()+-\\*\/\\ \\%]+$/, - error: 'Invalid - can only contain 0-9, x, y, spaces and the following symbols: <>&^|xy()+-%', + regex: /.*/, + error: 'Invalid expression', value: "(x ^ y) % 3", - dx: "JavaScript expression that uses x/y values to return a boolean value for each cell" + dx: "Maths expression that uses x/y values to return a boolean value for each cell, to make 'bitfield' patterns" }; const params = [warps, wefts, f]; @@ -42,22 +42,24 @@ const inlets = []; const perform = (param_vals: Array, op_inputs: Array) => { const num_warps: number = getOpParamValById(0, param_vals); const num_wefts: number = getOpParamValById(1, param_vals); - const script: string = getOpParamValById(2, param_vals); + let script: string = getOpParamValById(2, param_vals); + + // Mathjs uses ^ for pow, and ^| for bitwise xor + // This replaces ^ with ^|, so folks don't have to type the | + script = script.replace(/\^(?!\|)/, '^|'); + + // Evaluate as an expression with mathjs. This could just be done with a javascript eval(), but this is more secure. + let func = evaluate('f(x, y) = '.concat(script)); - // This should already have been checked but probably best to check here again in case - // a bad string could have been injected somewhere.. - if (script.match(f.regex)) { - let func = eval('(x, y) => '.concat(script)); - let pattern = new Sequence.TwoD(); - for (let weft = 0; weft < num_wefts; ++weft) { - const row = new Sequence.OneD(); - for (let warp = 0; warp < num_warps; ++warp) { - row.push(!! func(warp, weft)); - } - pattern.pushWeftSequence(row.val()); + let pattern = new Sequence.TwoD(); + for (let weft = 0; weft < num_wefts; ++weft) { + const row = new Sequence.OneD(); + for (let warp = 0; warp < num_warps; ++warp) { + row.push(!! func(warp, weft)); } - return Promise.resolve([initDraftFromDrawdown(pattern.export())]); + pattern.pushWeftSequence(row.val()); } + return Promise.resolve([initDraftFromDrawdown(pattern.export())]); } const generateName = (param_vals: Array, op_inputs: Array) : string => { From 1fa0a74cc88533c9b7580ec9539cea903a827cf3 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 4 Nov 2024 22:17:35 +0100 Subject: [PATCH 5/5] reformat --- src/app/core/operations/bitfield/bitfield.ts | 106 +++++++++++-------- 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/src/app/core/operations/bitfield/bitfield.ts b/src/app/core/operations/bitfield/bitfield.ts index d9fed5e13..df9bb9d93 100644 --- a/src/app/core/operations/bitfield/bitfield.ts +++ b/src/app/core/operations/bitfield/bitfield.ts @@ -1,38 +1,44 @@ -import { NumParam, StringParam, OpParamVal, OpInput, Operation } from "../../model/datatypes"; +import { + NumParam, + StringParam, + OpParamVal, + OpInput, + Operation, +} from "../../model/datatypes"; import { getOpParamValById } from "../../model/operations"; import { Sequence } from "../../model/sequence"; import { initDraftFromDrawdown } from "../../model/drafts"; -import { evaluate } from 'mathjs'; +import { evaluate } from "mathjs"; const name = "bitfield"; const old_names = []; //PARAMS -const warps:NumParam = - {name: 'num warps', - type: 'number', - min: 1, - max: 128, - value: 32, - dx: "Number of warps" +const warps: NumParam = { + name: "num warps", + type: "number", + min: 1, + max: 128, + value: 32, + dx: "Number of warps", }; -const wefts:NumParam = - {name: 'num warps', - type: 'number', - min: 1, - max: 128, - value: 32, - dx: "Number of wefts" +const wefts: NumParam = { + name: "num warps", + type: "number", + min: 1, + max: 128, + value: 32, + dx: "Number of wefts", }; -const f:StringParam = - {name: 'bitfield function', - type: 'string', - regex: /.*/, - error: 'Invalid expression', - value: "(x ^ y) % 3", - dx: "Maths expression that uses x/y values to return a boolean value for each cell, to make 'bitfield' patterns" +const f: StringParam = { + name: "bitfield function", + type: "string", + regex: /.*/, + error: "Invalid expression", + value: "(x ^ y) % 3", + dx: "Maths expression that uses x/y values to return a boolean value for each cell, to make 'bitfield' patterns", }; const params = [warps, wefts, f]; @@ -40,31 +46,41 @@ const params = [warps, wefts, f]; const inlets = []; const perform = (param_vals: Array, op_inputs: Array) => { - const num_warps: number = getOpParamValById(0, param_vals); - const num_wefts: number = getOpParamValById(1, param_vals); - let script: string = getOpParamValById(2, param_vals); + const num_warps: number = getOpParamValById(0, param_vals); + const num_wefts: number = getOpParamValById(1, param_vals); + let script: string = getOpParamValById(2, param_vals); - // Mathjs uses ^ for pow, and ^| for bitwise xor - // This replaces ^ with ^|, so folks don't have to type the | - script = script.replace(/\^(?!\|)/, '^|'); + // Mathjs uses ^ for pow, and ^| for bitwise xor + // This replaces ^ with ^|, so folks don't have to type the | + script = script.replace(/\^(?!\|)/, "^|"); - // Evaluate as an expression with mathjs. This could just be done with a javascript eval(), but this is more secure. - let func = evaluate('f(x, y) = '.concat(script)); + // Evaluate as an expression with mathjs. This could just be done with a javascript eval(), but this is more secure. + let func = evaluate("f(x, y) = ".concat(script)); - let pattern = new Sequence.TwoD(); - for (let weft = 0; weft < num_wefts; ++weft) { - const row = new Sequence.OneD(); - for (let warp = 0; warp < num_warps; ++warp) { - row.push(!! func(warp, weft)); - } - pattern.pushWeftSequence(row.val()); + let pattern = new Sequence.TwoD(); + for (let weft = 0; weft < num_wefts; ++weft) { + const row = new Sequence.OneD(); + for (let warp = 0; warp < num_warps; ++warp) { + row.push(!!func(warp, weft)); } - return Promise.resolve([initDraftFromDrawdown(pattern.export())]); -} + pattern.pushWeftSequence(row.val()); + } + return Promise.resolve([initDraftFromDrawdown(pattern.export())]); +}; -const generateName = (param_vals: Array, op_inputs: Array) : string => { - const num_up: number = getOpParamValById(0, param_vals); - return num_up + '/bitfield'; -} +const generateName = ( + param_vals: Array, + op_inputs: Array +): string => { + const num_up: number = getOpParamValById(0, param_vals); + return num_up + "/bitfield"; +}; -export const bitfield: Operation = {name, old_names, params, inlets, perform, generateName}; \ No newline at end of file +export const bitfield: Operation = { + name, + old_names, + params, + inlets, + perform, + generateName, +};