├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── docs └── index.html ├── examples ├── albers.html ├── code │ ├── css │ │ └── style.css │ ├── examples │ │ ├── albers1.js │ │ ├── albers2.js │ │ ├── arrows.js │ │ ├── astroid.js │ │ ├── bezier.js │ │ ├── bugs.js │ │ ├── clip-path.js │ │ ├── clock.js │ │ ├── curves.js │ │ ├── dragon.js │ │ ├── draw.js │ │ ├── hanoi.js │ │ ├── mask.js │ │ ├── parabola.js │ │ ├── polygon.js │ │ ├── riley1.js │ │ ├── spiral.js │ │ ├── star.js │ │ ├── ten-print.js │ │ ├── test1.js │ │ └── text.js │ ├── images │ │ ├── dice.svg │ │ ├── download.svg │ │ ├── font-size.svg │ │ ├── moon.svg │ │ ├── pause.svg │ │ ├── play.svg │ │ ├── question.svg │ │ └── share.svg │ ├── index.html │ └── js │ │ ├── app.js │ │ ├── live-code.js │ │ ├── lz-string.min.js │ │ ├── queryWatcher.js │ │ └── toolkit.js ├── compare-with.html ├── compare-without.html ├── empty.html ├── japanese-flag.html ├── load-file.html ├── mask.html ├── mystify.html ├── random-walker.html └── ten-print.html ├── package.json ├── readme.md ├── rollup.config.js ├── src ├── arguments │ ├── makeCoordinates.js │ ├── makeViewBox.js │ └── semiFlattenArrays.js ├── colors │ ├── convert.js │ ├── cssColors.js │ ├── index.js │ └── parseColor.js ├── constructor ├── environment │ ├── detect.js │ ├── lib.js │ ├── messages.js │ ├── strings.js │ └── window.js ├── general │ ├── algebra.js │ ├── cdata.js │ ├── dom.js │ ├── index.js │ ├── path.js │ ├── string.js │ ├── transforms.js │ └── viewBox.js ├── index.js └── spec │ ├── classes_attributes.js │ ├── classes_nodes.js │ ├── namespace.js │ ├── nodes.js │ ├── nodes_attributes.js │ └── nodes_children.js ├── svg.js ├── svg.module.js ├── tests ├── arrow.test.js ├── attributes.test.js ├── circle.test.js ├── class.id.test.js ├── clean.js ├── colors.test.js ├── coordinates.test.js ├── dom.test.js ├── dragon.svg ├── ear.js ├── ellipse.test.js ├── environment.test.js ├── index.html ├── line.test.js ├── lineno.js ├── nodes.test.js ├── object.assign.test.js ├── path.test.js ├── polygon.test.js ├── primitives.test.js ├── rect.test.js ├── stylesheet.test.js ├── svg.animation.test.js ├── svg.args.test.js ├── svg.background.test.js ├── svg.controls.test.js ├── svg.load.test.js ├── svg.save.test.js ├── svg.viewbox.test.js ├── transforms.test.js ├── types.test.js ├── urls.test.js ├── use.test.js └── window.test.js ├── tsconfig.json └── types ├── arguments ├── makeCoordinates.d.ts ├── makeViewBox.d.ts └── semiFlattenArrays.d.ts ├── colors ├── convert.d.ts ├── cssColors.d.ts ├── index.d.ts └── parseColor.d.ts ├── constructor ├── elements └── constructors.d.ts ├── environment ├── detect.d.ts ├── lib.d.ts ├── messages.d.ts ├── strings.d.ts └── window.d.ts ├── general ├── algebra.d.ts ├── cdata.d.ts ├── dom.d.ts ├── index.d.ts ├── path.d.ts ├── string.d.ts ├── transforms.d.ts └── viewBox.d.ts ├── index.d.ts └── spec ├── classes_attributes.d.ts ├── classes_nodes.d.ts ├── namespace.d.ts ├── nodes.d.ts ├── nodes_attributes.d.ts └── nodes_children.d.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": "airbnb-base", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "ecmaFeatures": { "jsx": true }, 11 | "sourceType": "module" 12 | }, 13 | "ignorePatterns": [ 14 | "svg.js", 15 | "svg.module.js", 16 | "svg.module.comments.js" 17 | ], 18 | "rules": { 19 | "arrow-parens": 0, 20 | "camelcase": ["error", { "allow": [".*"] }], 21 | "func-names": ["error", "never"], 22 | "indent": ["error", "tab"], 23 | "no-bitwise": 0, 24 | "no-continue": 0, 25 | "no-sparse-arrays": 0, 26 | "no-tabs": 0, 27 | "import/extensions": ["error", "always", { "ignorePackages": true }], 28 | "import/no-relative-packages": 0, 29 | "object-shorthand": 0, 30 | "object-curly-newline": 0, 31 | "import/prefer-default-export": 0, 32 | "prefer-rest-params": 0, 33 | "prefer-default-export": 0, 34 | "prefer-destructuring": 0, 35 | "quotes": ["error", "double", { "allowTemplateLiterals": true }] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules/ 3 | coverage/ 4 | module/ 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | .gitignore 4 | .travis.yml 5 | .eslintrc.js 6 | rollup.config.js 7 | coverage/ 8 | docs/ 9 | examples/ 10 | module/ 11 | include/ 12 | src/ 13 | tests/ 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* -------------------------------------------------------------------------------- /examples/albers.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: introduction 3 | 4 | 5 | 6 | 25 | -------------------------------------------------------------------------------- /examples/code/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | body { 6 | margin: 0; 7 | background-image: 8 | linear-gradient(45deg, #f8f8f8 25%, transparent 25%), 9 | linear-gradient(135deg, #f8f8f8 25%, transparent 25%), 10 | linear-gradient(45deg, transparent 75%, #f8f8f8 75%), 11 | linear-gradient(135deg, transparent 75%, #f8f8f8 75%); 12 | background-size: 25px 25px; 13 | background-position: 0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px; 14 | } 15 | svg { 16 | width: 100%; 17 | height: 100%; 18 | } 19 | #app { 20 | width: 100%; 21 | height: 100%; 22 | display: flex; 23 | flex-direction: row; 24 | } 25 | .code-editor, 26 | .code-canvas { 27 | flex: 1; 28 | } 29 | /* center everything on the canvas */ 30 | .code-canvas { 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | } 35 | .code-console { 36 | position: absolute; 37 | z-index: 2; 38 | bottom: 0; 39 | left: 0; 40 | width: 50%; 41 | } 42 | .code-console p { 43 | width: calc(100% - 8rem); 44 | margin: 1rem 4rem; 45 | padding: 0.5rem 1rem; 46 | background-color: #2f312a; 47 | border-radius: 0.5rem; 48 | color: #ddd; 49 | font-family: monospace; 50 | } 51 | /* buttons */ 52 | div[class*="-button"] { 53 | position: absolute; 54 | z-index: 8; 55 | width: 2rem; 56 | height: 2rem; 57 | border-radius: 1rem; 58 | cursor: pointer; 59 | background-repeat: no-repeat; 60 | background-size: auto; 61 | background-position: center center; 62 | } 63 | .play-pause-button { 64 | left: 0.5rem; 65 | bottom: 0.5rem; 66 | } 67 | .play-pause-button.pause { 68 | background-color: #5b23; 69 | background-image: url(../images/play.svg); 70 | } 71 | .play-pause-button.play { 72 | background-color: #5b2; 73 | background-image: url(../images/pause.svg); 74 | } 75 | .dark-mode-button { 76 | left: 0.5rem; 77 | bottom: 5.5rem; 78 | background-color: #fff9; 79 | background-image: url(../images/moon.svg); 80 | } 81 | .font-size-button { 82 | left: 0.5rem; 83 | bottom: 8rem; 84 | background-color: #fff9; 85 | background-image: url(../images/font-size.svg); 86 | } 87 | /* additional buttons */ 88 | .random-button { 89 | left: 0.5rem; 90 | bottom: 3rem; 91 | background-color: #fb3; 92 | background-image: url(../images/dice.svg); 93 | } 94 | .download-button { 95 | left: 0.5rem; 96 | bottom: 13rem; 97 | background-color: #fff9; 98 | background-image: url(../images/download.svg); 99 | } 100 | .share-button { 101 | left: 0.5rem; 102 | bottom: 10.5rem; 103 | background-color: #fff9; 104 | background-image: url(../images/share.svg); 105 | } 106 | .question-button { 107 | left: calc(50% - 2.5rem); 108 | top: 0.5rem; 109 | opacity: 0.8; 110 | background-color: #fff9; 111 | background-image: url(../images/question.svg); 112 | } 113 | @media not all and (hover), (max-aspect-ratio: 1/1) { 114 | .ace_editor { 115 | font-size: 1rem; 116 | } 117 | #app { 118 | flex-direction: column-reverse; 119 | } 120 | .code-console { 121 | width: 100%; 122 | } 123 | .code-editor, 124 | .code-canvas { 125 | height: 50%; 126 | } 127 | .play-pause-button { 128 | left: initial; 129 | right: 0.5rem; 130 | bottom: 0.5rem; 131 | } 132 | .random-button { 133 | left: initial; 134 | right: 3rem; 135 | bottom: 0.5rem; 136 | } 137 | .dark-mode-button { 138 | left: initial; 139 | right: 5.5rem; 140 | bottom: 0.5rem; 141 | } 142 | .font-size-button { 143 | left: initial; 144 | right: 8rem; 145 | bottom: 0.5rem; 146 | } 147 | .share-button { 148 | left: initial; 149 | right: 10.5rem; 150 | bottom: 0.5rem; 151 | } 152 | .download-button { 153 | left: initial; 154 | right: 13rem; 155 | bottom: 0.5rem; 156 | } 157 | .question-button { 158 | opacity: 1.0; 159 | top: initial; 160 | left: initial; 161 | right: 15.5rem; 162 | bottom: 0.5rem; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /examples/code/examples/albers1.js: -------------------------------------------------------------------------------- 1 | svg.size(90, 90); 2 | svg.background("#9590c0"); 3 | 4 | // all shapes will be drawn at the point 0, 0 5 | // shifting the layer will center the shapes 6 | var g = svg.g().translate(35, 15); 7 | 8 | // these attributes will be applied to every shape 9 | // this is useful when you have many similar-looking shapes 10 | var style = { fill: "#2c266d", opacity: 0.4 }; 11 | 12 | // transform-origin moves the center of rotation 13 | // right rect 14 | g.rect(30, 50) 15 | .setAttributes(style) 16 | .transformOrigin("0 50") 17 | .rotate(25); 18 | 19 | // no rotation 20 | // center rect 21 | g.rect(30, 50).setAttributes(style); 22 | 23 | // left rect 24 | g.rect(30, 50) 25 | .setAttributes(style) 26 | .transformOrigin("0 50") 27 | .rotate(-25); 28 | -------------------------------------------------------------------------------- /examples/code/examples/albers2.js: -------------------------------------------------------------------------------- 1 | svg.size(50, 60); 2 | svg.background("white"); 3 | 4 | svg.rect(50, 30).fill("steelblue"); 5 | svg.rect(50, 30).origin(0, 30).fill("peru"); 6 | svg.rect(10, 40).origin(20, 10).fill("#963"); // comment out this line 7 | svg.rect(50, 10).origin(0, 20).fill("midnightblue"); 8 | svg.rect(50, 10).origin(0, 30).fill("#fe6"); 9 | -------------------------------------------------------------------------------- /examples/code/examples/arrows.js: -------------------------------------------------------------------------------- 1 | svg.size(1, 1); 2 | svg.background("black"); 3 | 4 | let arrowhead = svg.marker() 5 | .setViewBox(0, -1, 2, 2) 6 | .orient("auto-start-reverse"); 7 | 8 | arrowhead.polygon(0, 1, 2, 0, 0, -1).fill("white"); 9 | 10 | for (var i = 0; i < 10; i += 1) { 11 | svg.curve(Math.random(), Math.random(), Math.random(), Math.random()) 12 | .fill("none") 13 | .stroke("white") 14 | .strokeWidth(0.02) 15 | .bend(0.5) 16 | .markerEnd(arrowhead) 17 | .markerStart(arrowhead); 18 | } 19 | -------------------------------------------------------------------------------- /examples/code/examples/astroid.js: -------------------------------------------------------------------------------- 1 | svg.size(-256, -256, 512, 512); 2 | svg.background("black"); 3 | 4 | // all children of this group inherit this style 5 | var layer = svg.g().stroke("white"); 6 | 7 | var segments = 30; 8 | 9 | var w = svg.getWidth() / 2 / segments; 10 | var h = svg.getHeight() / 2 / segments; 11 | 12 | // "i" will increment from 0 to (segments - 1) 13 | for (var i = 0; i < segments; i += 1) { 14 | // "j" will decrement from segments to 1 15 | var j = segments - i; 16 | layer.line(-w * i, 0, 0, -h * j); // top left 17 | layer.line(w * j, 0, 0, -h * i); // top right 18 | layer.line(w * i, 0, 0, h * j); // bottom right 19 | layer.line(-w * j, 0, 0, h * i); // bottom left 20 | } 21 | -------------------------------------------------------------------------------- /examples/code/examples/bezier.js: -------------------------------------------------------------------------------- 1 | svg.size(800, 800); 2 | 3 | // create a layer behind everything 4 | var back = svg.g(); 5 | 6 | // shapes on top 7 | var curve = svg.path(); 8 | var l1 = svg.line().stroke("black"); 9 | var l2 = svg.line().stroke("black"); 10 | 11 | // these are control points 12 | // 1.create an svg element to track with each point 13 | // 2.assign a position 14 | // 3.append to a parent 15 | // 4.onChange event handler with "this" bound to the controls 16 | svg.controls(4) 17 | .svg(function () { return SVG.circle(svg.getWidth() * 0.05).fill("#e53"); }) 18 | .position(function () { return [random(svg.getWidth()), random(svg.getHeight())]; }) 19 | .parent(back) 20 | .onChange(function () { 21 | l1.setPoints(this[0], this[1]); 22 | l2.setPoints(this[3], this[2]); 23 | curve.clear().Move(this[0]).Curve(this[1], this[2], this[3]); 24 | }, true); 25 | -------------------------------------------------------------------------------- /examples/code/examples/bugs.js: -------------------------------------------------------------------------------- 1 | svg.size(-1, -1, 2, 2); 2 | svg.background("black"); 3 | 4 | // dots and lines are separated to their own layers 5 | var lines = svg.g().strokeWidth(0.003); 6 | var dots = svg.g().fill("#ec3"); 7 | 8 | // the line endpoint, as vertices 0, 1, 2, 3 9 | var i = [0, 1]; 10 | var stroke = "#158"; 11 | 12 | svg.play = function (e) { 13 | // every 25th frame or so, change the line color 14 | // and which points the line connects 15 | if (Math.random() < 1/25) { 16 | i[0] = randomInt(4); 17 | i[1] = randomInt(4); 18 | stroke = random(["#158", "#e53"]); 19 | } 20 | dots.circle(noise(e.time + 0), noise(e.time + 100), 0.01); 21 | dots.circle(noise(e.time + 10), noise(e.time + 90), 0.01); 22 | dots.circle(noise(e.time + 20), noise(e.time + 80), 0.01); 23 | dots.circle(noise(e.time + 30), noise(e.time + 70), 0.01); 24 | lines.line( 25 | noise(e.time + 10 * i[0]), 26 | noise(e.time + 100 - 10 * i[0]), 27 | noise(e.time + 10 * i[1]), 28 | noise(e.time + 100 - 10 * i[1]) 29 | ).stroke(stroke); 30 | 31 | // limit number of lines and dots 32 | while (dots.childNodes.length > 60) { dots.firstChild.remove(); } 33 | while (lines.childNodes.length > 100) { lines.firstChild.remove(); } 34 | }; 35 | -------------------------------------------------------------------------------- /examples/code/examples/clip-path.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 100); 2 | svg.background("#edb"); 3 | 4 | // a clip-path will hide or show parts of shapes. 5 | // presence of a filled-shape indicates visibility 6 | var clip = svg.clipPath(); 7 | clip.circle(70.7).origin(50, 0); // the visible portion 8 | 9 | // three circles, untouched 10 | svg.circle(0, 50, 50).fill("#e53"); 11 | svg.circle(50, 50, 50).fill("#158").opacity(0.75); 12 | svg.circle(100, 50, 50).fill("#ec3"); 13 | 14 | // this circle will be clipped by the clip-path 15 | svg.circle(50, 100, 70.7) 16 | .fill("#e53") 17 | .opacity(0.75) 18 | .clipPath(clip); 19 | -------------------------------------------------------------------------------- /examples/code/examples/clock.js: -------------------------------------------------------------------------------- 1 | svg.size(-1, -1, 2, 2); 2 | svg.background("#888"); 3 | var radius = svg.getWidth() * 0.48; 4 | 5 | for (var i = 0; i < 12; i += 1) { 6 | svg.text(String((i + 11) % 12 + 1)) 7 | .fontFamily("Times New Roman") 8 | .fontSize(svg.getWidth() / 8) 9 | .textAnchor("middle") 10 | .setAttribute("style", "transform: rotate("+(i*30)+"deg) translate(0, -"+radius*0.75+"px)"); 11 | } 12 | 13 | var pies = [ 14 | svg.wedge().fill("#0008"), 15 | svg.wedge().fill("transparent"), 16 | svg.wedge().fill("#fff8") 17 | ]; 18 | 19 | svg.play = function (time) { 20 | var d = new Date(); 21 | var s = (d.getSeconds() + d.getMilliseconds() / 1000) / 60; 22 | var m = d.getMinutes() / 60; 23 | var h = (d.getHours() % 12) / 12; 24 | [(s), (m + s / 60), (h + m / 12 + s / 720)] 25 | .sort(function(a, b) { return a - b; }) 26 | .forEach(function(a, i, arr) { 27 | var a1 = -PI / 2 + 2 * PI * a; 28 | var a2 = -PI / 2 + 2 * PI * arr[(i + 1) % arr.length]; 29 | pies[i].setArc(0, 0, radius, a1, a2, true); 30 | }); 31 | }; -------------------------------------------------------------------------------- /examples/code/examples/curves.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 100); 2 | svg.background("white"); 3 | 4 | var colors = ["#e53", "#158", "#ec3"]; 5 | 6 | var pts = []; 7 | for (var i = 0; i < 8; i += 1) { 8 | pts.push([Math.random() * 100, Math.random() * 100]); 9 | } 10 | 11 | for (var i = 0; i < pts.length - 1; i += 1) { 12 | var color = colors[Math.floor(Math.random()*3)]; 13 | var width = Math.random() * 12; 14 | var rand1 = Math.random() < 0.5; 15 | var rand2 = Math.random() < 0.8; 16 | 17 | svg.curve(pts[i], pts[i+1]) 18 | .fill("none") 19 | .stroke(color) 20 | .strokeWidth(width) 21 | .strokeDasharray(rand2 ? "none" : Math.random() * 8 + 2) 22 | .bend(rand1 ? 0 : 0.5); 23 | } 24 | -------------------------------------------------------------------------------- /examples/code/examples/dragon.js: -------------------------------------------------------------------------------- 1 | svg.size(600, 600); 2 | svg.background("white", true); 3 | 4 | function dragon(x1, y1, x2, y2, turn, i) { 5 | if (i < 0) { return [[x1, y1], [x2, y2]]; } 6 | var midX = x1 + (x2 - x1) * 0.5 + turn * (y2 - y1) * 0.5; 7 | var midY = y1 + (y2 - y1) * 0.5 + (-1 * turn) * (x2 - x1) * 0.5; 8 | var first = dragon(x1, y1, midX, midY, 1, i - 1); 9 | if (first.length > 1) { first.pop(); } 10 | return first.concat(dragon(midX, midY, x2, y2, -1, i - 1)); 11 | } 12 | 13 | var attrs = { strokeLinecap: "square", fill: "none" }; 14 | 15 | var x1 = svg.getWidth() * 0.25; 16 | var y1 = svg.getHeight() * 0.6; 17 | var x2 = svg.getWidth() * 0.85; 18 | var y2 = svg.getHeight() * 0.6; 19 | 20 | svg.polyline(dragon(x1, y1, x2, y2, 1, random(4, 8))) 21 | .setAttributes(attrs).stroke("#ec3").strokeWidth(7); 22 | svg.polyline(dragon(x1, y1, x2, y2, 1, random(10, 12))) 23 | .setAttributes(attrs).stroke("#158").strokeWidth(3); 24 | -------------------------------------------------------------------------------- /examples/code/examples/draw.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 100); 2 | 3 | // keep track of every touch 4 | var points = []; 5 | 6 | // a polygon is a filled shape 7 | // defined by an array of points 8 | var p = svg.polygon() 9 | .fillRule("evenodd") 10 | .fill("#e53") 11 | .stroke("#158"); 12 | // "evenodd" is a cool effect for 13 | // when the polygon self-intersects 14 | 15 | // every time a touch moves, add a point to the array 16 | svg.onMove = function (mouse) { 17 | points.push([mouse.x, mouse.y]); 18 | // only 100 points are allowed 19 | if (points.length > 100) { points.shift(); } 20 | // update the polygon with the current set of points 21 | p.setPoints(points); 22 | }; 23 | 24 | /////////////// 25 | // // 26 | // draw 🌀🖌 // 27 | // // 28 | /////////////// 29 | -------------------------------------------------------------------------------- /examples/code/examples/hanoi.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 33); 2 | 3 | const DISKS = 7; 4 | const SPEED = 4; // (int) lower is faster 5 | let steps = []; 6 | let left = Array.from(Array(DISKS)) 7 | .map(function(_, i) { return DISKS-i; }); 8 | const poles = [left, [], []]; 9 | 10 | var resetTimer = undefined; 11 | var resetCount = 0; 12 | 13 | const resetFunc = () => { 14 | resetCount++; 15 | resetTimer = undefined; 16 | frame = 0; 17 | step = 0; 18 | steps = []; 19 | poles[0] = resetCount % 2 === 0 ? Array.from(Array(DISKS)).map((_, i) => DISKS-i) : []; 20 | poles[1] = []; 21 | poles[2] = resetCount % 2 === 0 ? [] : Array.from(Array(DISKS)).map((_, i) => DISKS-i); 22 | if (resetCount % 2 === 0) { 23 | Hanoi(DISKS, 0, 1, 2); 24 | } else { 25 | Hanoi(DISKS, 2, 1, 0); 26 | } 27 | }; 28 | 29 | const Hanoi = function (disk, from, buffer, to) { 30 | if (disk <= 0) { return; } 31 | Hanoi(disk - 1, from, to, buffer); 32 | steps.push({ from, to }); 33 | Hanoi(disk - 1, buffer, from, to); 34 | }; 35 | Hanoi(DISKS, 0, 1, 2); 36 | 37 | const draw = function () { 38 | var thick = 4; 39 | svg.removeChildren(); 40 | poles.forEach(function (pole, p) { 41 | var x = 25*(p+1); 42 | svg.line(x, 0, x, svg.getHeight()).stroke("black"); 43 | pole.forEach(function (plate, i) { 44 | var w = plate * 25/DISKS; 45 | svg.rect(x-w/2, svg.getHeight()-(i+1)*thick - 0.5, w, thick) 46 | .stroke("black") 47 | .fill("hsl("+(36/DISKS*(plate-1))+",80%,45%)"); 48 | }); 49 | }); 50 | }; 51 | 52 | let frame = 0; 53 | let step = 0; 54 | svg.play = function (e) { 55 | // if (step >= steps.length) { svg.stop(); } 56 | if (step >= steps.length) { 57 | if (!resetTimer) { resetTimer = setTimeout(resetFunc, 1000); } 58 | return; 59 | } 60 | if (frame % SPEED === 0) { 61 | poles[steps[step].to].push(poles[steps[step].from].pop()); 62 | draw(); 63 | step++; 64 | } 65 | frame++; 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /examples/code/examples/mask.js: -------------------------------------------------------------------------------- 1 | svg.size(1, 1); 2 | svg.background("#edb"); 3 | 4 | // a mask will hide or show parts of shapes 5 | // depending on the contents. white: visible, black: invisible 6 | var maskA = svg.mask(); 7 | var maskB = svg.mask(); 8 | 9 | // a polygon from 100 connected random points 10 | var points = Array.from(Array(100)) 11 | .map(() => [random(-1, 2), random(-1, 2)]); 12 | 13 | // each mask gets the same polygon 14 | // but the polygon and background color alternate 15 | // white/black and black/white 16 | maskA.polygon(points).fill("white").fillRule("evenodd"); 17 | maskB.rect(1, 1).fill("white"); 18 | maskB.polygon(points).fill("black").fillRule("evenodd"); 19 | 20 | // two circles, assigned to different masks 21 | svg.circle(random(), random(), 0.5) 22 | .fill("black") 23 | .mask(maskA); 24 | 25 | svg.circle(random(), random(), 0.5) 26 | .fill("#e53") 27 | .mask(maskB); 28 | -------------------------------------------------------------------------------- /examples/code/examples/parabola.js: -------------------------------------------------------------------------------- 1 | svg.size(-50, -50, 100, 100); 2 | 3 | svg.parabola().fill("#000a").rotate(0).translate(0, -30).scale(30, 60); 4 | svg.parabola().fill("#ec3a").rotate(90).translate(0, -30).scale(30, 60); 5 | svg.parabola().fill("#158a").rotate(270).translate(0, -30).scale(30, 60); 6 | svg.parabola().fill("#e53a").rotate(180).translate(0, -30).scale(30, 60); 7 | 8 | svg.rect(-30, -30, 60, 60).fill("none").stroke("black"); 9 | -------------------------------------------------------------------------------- /examples/code/examples/polygon.js: -------------------------------------------------------------------------------- 1 | svg.size(-1, -1, 2, 2); 2 | svg.background("white"); 3 | 4 | // all children will inherit this style 5 | svg.stroke("black") 6 | .strokeWidth(0.001) 7 | .fill("none"); 8 | 9 | // Math.tan(Math.PI/i) / Math.sin(Math.PI/i); 10 | 11 | for (var i = 3; i < 36; i += 1) { 12 | // all polygons are vertex-aligned along the +X axis 13 | // a -90 degree rotation aligns it with the Y 14 | svg.regularPolygon(i).rotate(-90); 15 | } 16 | -------------------------------------------------------------------------------- /examples/code/examples/riley1.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 100); 2 | 3 | var space = random(3, 6); 4 | var clip = svg.clipPath(); 5 | clip.rect(100, 100); 6 | 7 | for (var i = 0; i < (100 + space) / space; i += 1) { 8 | let p = svg.path() 9 | .fill("none") 10 | .strokeWidth(space) 11 | .strokeLinecap("square") 12 | .stroke(i % 2 === 0 ? "#000" : "#fff") 13 | .clipPath(clip) 14 | .Move(i * space, 0); 15 | 16 | for (var j = 0; j < 10; j += 1) { 17 | var dir = (j % 2) ? 0.5 : -0.5; 18 | var controlA = [(i + dir) * space, (j + 0.333) * 10]; 19 | var controlB = [(i + dir) * space, (j + 0.666) * 10]; 20 | var end = [i * space, (j + 1) * 10]; 21 | p.Curve(controlA, controlB, end); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/code/examples/spiral.js: -------------------------------------------------------------------------------- 1 | svg.size(-10, -10, 20, 20); 2 | svg.background('white'); 3 | 4 | var phipi = Math.PI / (Math.sqrt(5) + 1) * 4; 5 | 6 | var radius = 1 / 40; 7 | var size = 1 / 30; 8 | 9 | for(var i = 1; i < 360; i += 1) { 10 | var x = i * radius; 11 | var r = Math.pow(i, 0.5) * size; 12 | svg.regularPolygon(3, x, 0, r) 13 | .rotate(i * phipi * 180 / Math.PI) 14 | .fill("#000"); 15 | } 16 | -------------------------------------------------------------------------------- /examples/code/examples/star.js: -------------------------------------------------------------------------------- 1 | var frametime = 0; 2 | var framerates = []; 3 | 4 | svg.size(-5, -5, 10, 10); 5 | 6 | svg.play = function (t) { 7 | var rate = t.time - frametime; 8 | framerates.push(rate); 9 | while(framerates.length > 60) { 10 | framerates.shift(); 11 | } 12 | var avg = framerates 13 | .reduce(function(a, b) { return a+b; },0) 14 | / framerates.length; 15 | frametime = t.time; 16 | 17 | var starpoints = Array.from(Array(1000)).map(function() { 18 | return [Math.random()*0.02, Math.random()*0.02]; 19 | }); 20 | 21 | svg.removeChildren(); 22 | svg.background("black"); 23 | svg.polyline(starpoints) 24 | .stroke("white") 25 | .strokeWidth(Math.cos(t.time) + 1.5); 26 | 27 | // framerate 28 | var frameString = String((1/avg).toFixed(1)); 29 | svg.text(frameString,-5,-5) 30 | .dominantBaseline("hanging") 31 | .fill("white") 32 | .fontSize(0.4) 33 | .fontWeight(900); 34 | }; -------------------------------------------------------------------------------- /examples/code/examples/ten-print.js: -------------------------------------------------------------------------------- 1 | svg.size(40, 25); 2 | svg.background("#329"); 3 | 4 | // apply style to the layer and all children will inherit 5 | var layer = svg.g() 6 | .stroke("#76d") 7 | .strokeWidth("1%") 8 | .strokeLinecap("square"); 9 | 10 | // a 2d-grid of either / or \ 11 | // the column and row sizes are 1px 12 | for (var y = 0; y < 25; y += 1) { 13 | for (var x = 0; x < 40; x += 1) { 14 | // a random 0 or 1, and "b" will be the opposite 15 | var a = Math.random() - 0.5 > 0 ? 0 : 1; 16 | var b = a ? 0 : 1; 17 | // a diagonal line, upwards or downwards 18 | layer.line(x, y + a, x + 1, y + b); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/code/examples/test1.js: -------------------------------------------------------------------------------- 1 | var frametime = 0; 2 | var framerates = []; 3 | 4 | var testMask = function () { 5 | // svg.size(0, 0, 10, 10); 6 | svg.size(0, 0, 1, 1); 7 | // svg.size(.2, .2, .6, .6); 8 | 9 | svg.play = function (t) { 10 | // console.log(t) 11 | var rate = t.time - frametime; 12 | framerates.push(rate); 13 | while(framerates.length > 50) { 14 | framerates.shift(); 15 | } 16 | const avg = framerates 17 | .reduce(function(a, b) { return a+b; },0) 18 | / framerates.length; 19 | frametime = t.time; 20 | svg.removeChildren(); 21 | svg.background("white"); 22 | 23 | let points = Array.from(Array(1000)) 24 | .map(() => [Math.random(), Math.random()]); 25 | svg.polygon(points).fill("black");//.fillRule("evenodd"); 26 | 27 | var frameString = String((1/avg).toFixed(3)); 28 | 29 | svg.text(frameString,0,0.05) 30 | .fill("white") 31 | .fontSize(0.05) 32 | .fontWeight(900) 33 | .fontFamily("Helvetica"); 34 | svg.text(frameString,0,0.05) 35 | .fontSize(0.05) 36 | .fontWeight(100) 37 | .fontFamily("Helvetica"); 38 | }; 39 | }; 40 | 41 | testMask(); -------------------------------------------------------------------------------- /examples/code/examples/text.js: -------------------------------------------------------------------------------- 1 | svg.size(100, 100); 2 | svg.background("black", true); 3 | 4 | var style = { 5 | fontFamily: "avenir next, helvetica neue, arial", 6 | fontWeight: 400, 7 | fontSize: "16px", 8 | textAnchor: "middle" 9 | }; 10 | 11 | for (var j = 0; j < 7; j += 1) { 12 | for (var i = 0; i < 9; i += 1) { 13 | var x = 50 + 2 - 0.5 * i; 14 | var y = 0 + j * 18 + 0.5 * i; 15 | svg.text(["los angeles", "new york"][j % 2], x, y) 16 | .setAttributes(style) 17 | .fill(`rgba(255, 0, ${150 + j * 15}, 0.25)`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/code/images/dice.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/images/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/images/font-size.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/code/images/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/images/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/images/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/images/question.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/code/images/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ✨SVG✨ 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/code/js/queryWatcher.js: -------------------------------------------------------------------------------- 1 | const QueryWatcher = function (queryKey) { 2 | const { LZString } = window; 3 | const app = {}; 4 | const url = new URL(window.location.href); 5 | 6 | const getURLQuery = function () { 7 | // do we need to call url = new URL(window.location.href); 8 | const c = url.searchParams.get(queryKey); 9 | if (c !== null) { 10 | const decoded = LZString.decompressFromEncodedURIComponent(c); 11 | if (decoded == null) { 12 | // bad decoding. user error. 13 | } 14 | return decoded; 15 | } 16 | return ""; 17 | }; 18 | 19 | const makeURLWithQueryValue = function (string) { 20 | const encoded = LZString.compressToEncodedURIComponent(string); 21 | return (encoded == null || string == null || string === "") 22 | ? `${url.origin}${url.pathname}` 23 | : `${url.origin}${url.pathname}?${queryKey}=${encoded}`; 24 | }; 25 | 26 | const setURLQuery = function (rawText) { 27 | const newURL = makeURLWithQueryValue(rawText); 28 | window.history.replaceState(null, null, newURL); 29 | }; 30 | 31 | Object.defineProperty(app, "value", { 32 | get: () => getURLQuery(), 33 | set: (newValue) => { setURLQuery(newValue); } 34 | }); 35 | app.compress = (...args) => LZString.compressToEncodedURIComponent(...args); 36 | app.uncompress = (...args) => LZString.decompressFromEncodedURIComponent(...args); 37 | app.makeURLWithQueryValue = makeURLWithQueryValue; 38 | 39 | return app; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/compare-with.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | SVG example: introduction 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/compare-without.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | SVG example: introduction 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/empty.html: -------------------------------------------------------------------------------- 1 | 2 | . 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/japanese-flag.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: Japanese flag 3 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /examples/load-file.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: load an SVG file 3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/mask.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: introduction 3 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /examples/mystify.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: introduction 3 | 4 | 5 | 6 | 58 | -------------------------------------------------------------------------------- /examples/random-walker.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: random walker 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/ten-print.html: -------------------------------------------------------------------------------- 1 | 2 | SVG example: 10PRINT 3 | 4 | 7 | 8 | 9 | 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rabbit-ear-svg", 3 | "version": "0.2.3", 4 | "description": "creative coding with SVG", 5 | "main": "svg.js", 6 | "type": "module", 7 | "sourceType": "module", 8 | "types": "./types/index.d.ts", 9 | "scripts": { 10 | "test": "vitest", 11 | "clean": "node tests/clean.js", 12 | "lineno": "node tests/lineno.js" 13 | }, 14 | "author": "Robby Kraft", 15 | "license": "MIT", 16 | "repository": "https://github.com/robbykraft/SVG", 17 | "keywords": [ 18 | "svg", 19 | "art", 20 | "vector", 21 | "graphics", 22 | "creative", 23 | "code", 24 | "illustrator", 25 | "animation", 26 | "script", 27 | "library" 28 | ], 29 | "devDependencies": { 30 | "@rollup/plugin-terser": "^0.4.3", 31 | "@xmldom/xmldom": "^0.8.10", 32 | "eslint": "^8.47.0", 33 | "eslint-config-airbnb-base": "^15.0.0", 34 | "eslint-plugin-import": "^2.28.1", 35 | "rollup": "^3.28.1", 36 | "rollup-plugin-cleanup": "^3.2.1", 37 | "typedoc": "^0.25.13", 38 | "typescript": "^5.4.4", 39 | "vitest": "^0.34.6" 40 | }, 41 | "jest": { 42 | "collectCoverage": true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SVG 2 | 3 | [![Build Status](https://travis-ci.org/robbykraft/SVG.svg?branch=master)](https://travis-ci.org/robbykraft/SVG) 4 | 5 | creative coding with SVG 6 | 7 | *easy drawing and styling, event handlers, browser or node.js* 8 | 9 | ## Examples 10 | 11 | [Code editor](https://robbykraft.github.io/SVG/examples/code/), a live code editor which includes examples (roll the dice). 12 | 13 | [Download](https://github.com/robbykraft/SVG/releases), and there are more examples in the `examples/` folder. 14 | 15 | ## Install 16 | 17 | The compiled library is one file, and works in the browser or in Node. 18 | 19 | ``` 20 | https://robbykraft.github.io/SVG/svg.js 21 | ``` 22 | 23 | ``` 24 | npm i rabbit-ear-svg 25 | ``` 26 | 27 | ## Usage 28 | 29 | Two sources of documentation: 30 | 31 | [SVG docs](https://robbykraft.github.io/SVG/docs/) 32 | 33 | [rabbit ear docs](https://rabbitear.org/book/svg.html) 34 | 35 | ## Credit 36 | 37 | - [vkBeautify](https://github.com/vkiryukhin/vkBeautify) pretty-print for SVG export 38 | - [XML DOM](https://github.com/xmldom/xmldom) for a "window" object in Node 39 | 40 | ## License 41 | 42 | MIT 43 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import cleanup from "rollup-plugin-cleanup"; 2 | import terser from "@rollup/plugin-terser"; 3 | 4 | const input = "src/index.js"; 5 | const name = "SVG"; 6 | const banner = "/* SVG (c) Kraft */"; 7 | 8 | export default [{ 9 | input, 10 | output: { 11 | name, 12 | file: "svg.js", 13 | format: "umd", 14 | banner, 15 | compact: true, 16 | generatedCode: { 17 | constBindings: true, 18 | objectShorthand: true, 19 | }, 20 | }, 21 | plugins: [cleanup(), terser()], 22 | // plugins: [cleanup()], 23 | }, { 24 | input, 25 | output: { 26 | name, 27 | file: "svg.module.js", 28 | format: "es", 29 | banner, 30 | generatedCode: { 31 | constBindings: true, 32 | objectShorthand: true, 33 | }, 34 | }, 35 | plugins: [cleanup()], 36 | }, { 37 | input, 38 | output: { 39 | name, 40 | dir: "module/", 41 | format: "es", 42 | banner, 43 | preserveModules: true, 44 | generatedCode: { 45 | constBindings: true, 46 | objectShorthand: true, 47 | }, 48 | }, 49 | }]; 50 | -------------------------------------------------------------------------------- /src/arguments/makeCoordinates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | str_number, 6 | str_object, 7 | } from "../environment/strings.js"; 8 | /** 9 | * this will extract coordinates from a set of inputs 10 | * and present them as a stride-2 flat array. length % 2 === 0 11 | * a 1D array of numbers, alternating x y 12 | * 13 | * use flatten() everytime you call this! 14 | * it's necessary the entries sit at the top level of ...args 15 | * findCoordinates(...flatten(...args)); 16 | */ 17 | const makeCoordinates = (...args) => args 18 | .filter(a => typeof a === str_number) 19 | .concat(args 20 | .filter(a => typeof a === str_object && a !== null) 21 | .map((el) => { 22 | if (typeof el.x === str_number) { return [el.x, el.y]; } 23 | if (typeof el[0] === str_number) { return [el[0], el[1]]; } 24 | return undefined; 25 | }).filter(a => a !== undefined) 26 | .reduce((a, b) => a.concat(b), [])); 27 | // [top-level numbers] concat [{x:,y:} and [0,1]] style 28 | 29 | export default makeCoordinates; 30 | -------------------------------------------------------------------------------- /src/arguments/makeViewBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "./makeCoordinates.js"; 5 | 6 | /** 7 | * @param {number} x 8 | * @param {number} y 9 | * @param {number} width 10 | * @param {number} height 11 | * @param {number} [padding=0] 12 | * @returns {string} 13 | */ 14 | const viewBoxValuesToString = function (x, y, width, height, padding = 0) { 15 | const scale = 1.0; 16 | const d = (width / scale) - width; 17 | const X = (x - d) - padding; 18 | const Y = (y - d) - padding; 19 | const W = (width + d * 2) + padding * 2; 20 | const H = (height + d * 2) + padding * 2; 21 | return [X, Y, W, H].join(" "); 22 | }; 23 | 24 | /** 25 | * @returns {string | undefined} 26 | */ 27 | const makeViewBox = (...args) => { 28 | const nums = makeCoordinates(...args.flat()); 29 | if (nums.length === 2) { nums.unshift(0, 0); } 30 | return nums.length === 4 31 | ? viewBoxValuesToString(nums[0], nums[1], nums[2], nums[3]) 32 | : undefined; 33 | }; 34 | 35 | export default makeViewBox; 36 | -------------------------------------------------------------------------------- /src/arguments/semiFlattenArrays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | str_function, 6 | str_string, 7 | } from "../environment/strings.js"; 8 | 9 | const svgIsIterable = (obj) => obj != null 10 | && typeof obj[Symbol.iterator] === str_function; 11 | 12 | /** 13 | * @description flatten only until the point of comma separated entities. 14 | * This will preserve vectors (number[]) in an array of array of vectors. 15 | * @param {any[][]} args any array, intended to contain arrays of arrays. 16 | * @returns {array[]} a flattened copy, flattened up until the point before 17 | * combining arrays of elements. 18 | */ 19 | export const svgSemiFlattenArrays = function () { 20 | switch (arguments.length) { 21 | case 0: return Array.from(arguments); 22 | // only if its an array (is iterable) and NOT a string 23 | case 1: return svgIsIterable(arguments[0]) && typeof arguments[0] !== str_string 24 | ? svgSemiFlattenArrays(...arguments[0]) 25 | : [arguments[0]]; 26 | default: 27 | return Array.from(arguments).map(a => (svgIsIterable(a) 28 | ? [...svgSemiFlattenArrays(a)] 29 | : a)); 30 | } 31 | }; 32 | export default svgSemiFlattenArrays; 33 | -------------------------------------------------------------------------------- /src/colors/convert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | /** 6 | * @param {number} n 7 | */ 8 | const roundF = n => Math.round(n * 100) / 100; 9 | 10 | /** 11 | * @description Convert hue-saturation-lightness values into 12 | * three RGB values, each between 0 and 1 (not 0-255). 13 | * @param {number} hue value between 0 and 360 14 | * @param {number} saturation value between 0 and 100 15 | * @param {number} lightness value between 0 and 100 16 | * @param {number | undefined} alpha the alpha component from 0 to 1 17 | * @returns {number[]} three values between 0 and 255, or four 18 | * if an alpha value is provided, where the fourth is between 0 and 1. 19 | * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10 20 | */ 21 | export const hslToRgb = (hue, saturation, lightness, alpha) => { 22 | const s = saturation / 100; 23 | const l = lightness / 100; 24 | /** @param {number} n */ 25 | const k = n => (n + hue / 30) % 12; 26 | const a = s * Math.min(l, 1 - l); 27 | /** @param {number} n */ 28 | const f = n => ( 29 | l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) 30 | ); 31 | return alpha === undefined 32 | ? [f(0) * 255, f(8) * 255, f(4) * 255] 33 | : [f(0) * 255, f(8) * 255, f(4) * 255, alpha]; 34 | }; 35 | 36 | /** 37 | * 38 | */ 39 | const mapHexNumbers = (numbers, map) => { 40 | // ensure a minimum number of characters (fill 0 if needed) 41 | const chars = Array.from(Array(map.length)) 42 | .map((_, i) => numbers[i] || "0"); 43 | // handle abbreviated hex codes: #fb4 or #fb48 (with alpha) 44 | return numbers.length <= 4 45 | ? map.map(i => chars[i]).join("") 46 | : chars.join(""); 47 | }; 48 | 49 | /** 50 | * @description Convert a hex string into an array of 51 | * three numbers, the rgb values (between 0 and 1). 52 | * This ignores any alpha values. 53 | * @param {string} string a hex color code as a string 54 | * @returns {number[]} three values between 0 and 255 55 | * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10 56 | */ 57 | export const hexToRgb = (string) => { 58 | const numbers = string.replace(/#(?=\S)/g, ""); 59 | const hasAlpha = numbers.length === 4 || numbers.length === 8; 60 | const hexString = hasAlpha 61 | ? mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2, 3, 3]) 62 | : mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2]); 63 | const c = parseInt(hexString, 16); 64 | return hasAlpha 65 | ? [(c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, roundF((c & 255) / 256)] 66 | : [(c >> 16) & 255, (c >> 8) & 255, c & 255]; 67 | }; 68 | 69 | /** 70 | * @param {number} red the red component from 0 to 255 71 | * @param {number} green the green component from 0 to 255 72 | * @param {number} blue the blue component from 0 to 255 73 | * @param {number | undefined} alpha the alpha component from 0 to 1 74 | * @returns {string} hex string, with our without alpha. 75 | */ 76 | export const rgbToHex = (red, green, blue, alpha) => { 77 | /** @param {number} n */ 78 | const to16 = n => `00${Math.max(0, Math.min(Math.round(n), 255)).toString(16)}` 79 | .slice(-2); 80 | const hex = `#${[red, green, blue].map(to16).join("")}`; 81 | return alpha === undefined 82 | ? hex 83 | : `${hex}${to16(alpha * 255)}`; 84 | }; 85 | -------------------------------------------------------------------------------- /src/colors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import cssColors from "./cssColors.js"; 5 | import * as convert from "./convert.js"; 6 | import * as parseColor from "./parseColor.js"; 7 | 8 | export default { 9 | cssColors, 10 | ...convert, 11 | ...parseColor, 12 | }; 13 | -------------------------------------------------------------------------------- /src/colors/parseColor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import cssColors from "./cssColors.js"; 5 | import { 6 | hexToRgb, 7 | hslToRgb, 8 | rgbToHex, 9 | } from "./convert.js"; 10 | 11 | /** 12 | * 13 | */ 14 | const getParenNumbers = str => { 15 | const match = str.match(/\(([^\)]+)\)/g); 16 | if (match == null || !match.length) { return []; } 17 | return match[0] 18 | .substring(1, match[0].length - 1) 19 | .split(/[\s,]+/) 20 | .map(parseFloat); 21 | }; 22 | 23 | /** 24 | * @description input a color as a string and get back the RGB 25 | * values as three numbers in an array. This supports CSS/SVG 26 | * color strings like named colors, hex colors, rgb(), hsl(). 27 | * @param {string} string a CSS/SVG color string in any form 28 | * @returns {number[] | undefined} red green blue values between 0 and 255, 29 | * with possible 4th value between 0 and 1. 30 | */ 31 | export const parseColorToRgb = (string) => { 32 | if (cssColors[string]) { return hexToRgb(cssColors[string]); } 33 | if (string[0] === "#") { return hexToRgb(string); } 34 | if (string.substring(0, 4) === "rgba" 35 | || string.substring(0, 3) === "rgb") { 36 | const values = getParenNumbers(string); 37 | [0, 1, 2] 38 | .filter(i => values[i] === undefined) 39 | .forEach(i => { values[i] = 0; }); 40 | return values; 41 | } 42 | if (string.substring(0, 4) === "hsla" 43 | || string.substring(0, 3) === "hsl") { 44 | const values = getParenNumbers(string); 45 | [0, 1, 2] 46 | .filter(i => values[i] === undefined) 47 | .forEach(i => { values[i] = 0; }); 48 | return hslToRgb(values[0], values[1], values[2], values[3]); 49 | } 50 | return undefined; 51 | }; 52 | 53 | /** 54 | * @description input a color as a string and return the 55 | * same color as a hex value string. This supports CSS/SVG 56 | * color strings like named colors, hex colors, rgb(), hsl(). 57 | * @param {string} string a CSS/SVG color string in any form 58 | * @returns {string} a hex-color form of the input color string. 59 | */ 60 | export const parseColorToHex = (string) => { 61 | if (cssColors[string]) { return cssColors[string].toUpperCase(); } 62 | // convert back and forth, this converts 3 or 4 digit hex to 6 or 8. 63 | if (string[0] === "#") { 64 | const [r, g, b, a] = hexToRgb(string); 65 | return rgbToHex(r, g, b, a); 66 | } 67 | if (string.substring(0, 4) === "rgba" 68 | || string.substring(0, 3) === "rgb") { 69 | const [r, g, b, a] = getParenNumbers(string); 70 | return rgbToHex(r, g, b, a); 71 | } 72 | if (string.substring(0, 4) === "hsla" 73 | || string.substring(0, 3) === "hsl") { 74 | const values = getParenNumbers(string); 75 | [0, 1, 2] 76 | .filter(i => values[i] === undefined) 77 | .forEach(i => { values[i] = 0; }); 78 | const [h, s, l, a] = values; 79 | const [r, g, b] = hslToRgb(h, s, l, a); 80 | return rgbToHex(r, g, b, a); 81 | } 82 | return undefined; 83 | }; 84 | -------------------------------------------------------------------------------- /src/constructor/extensions/arc/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeArcPath from "../shared/makeArcPath.js"; 5 | import { str_path } from "../../../environment/strings.js"; 6 | import TransformMethods from "../shared/transforms.js"; 7 | 8 | const arcArguments = (a, b, c, d, e) => [makeArcPath(a, b, c, d, e, false)]; 9 | 10 | export default { 11 | arc: { 12 | nodeName: str_path, 13 | attributes: ["d"], 14 | args: arcArguments, 15 | methods: { 16 | setArc: (el, ...args) => el.setAttribute("d", arcArguments(...args)), 17 | ...TransformMethods, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import ArrowMethods from "./methods.js"; 5 | import init from "./init.js"; 6 | 7 | export default { 8 | arrow: { 9 | nodeName: "g", 10 | attributes: [], 11 | args: () => [], // one function 12 | methods: ArrowMethods, // object of functions 13 | init, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | str_object, 6 | str_arrow, 7 | str_class, 8 | str_path, 9 | str_tail, 10 | str_head, 11 | str_style, 12 | str_stroke, 13 | str_none, 14 | } from "../../../environment/strings.js"; 15 | import NS from "../../../spec/namespace.js"; 16 | import window from "../../../environment/window.js"; 17 | import ArrowMethods from "./methods.js"; 18 | import { makeArrowOptions } from "./options.js"; 19 | // import Library from "../../../library.js"; 20 | 21 | const arrowKeys = Object.keys(makeArrowOptions()); 22 | 23 | const matchingOptions = (...args) => { 24 | for (let a = 0; a < args.length; a += 1) { 25 | if (typeof args[a] !== str_object) { continue; } 26 | const keys = Object.keys(args[a]); 27 | for (let i = 0; i < keys.length; i += 1) { 28 | if (arrowKeys.includes(keys[i])) { 29 | return args[a]; 30 | } 31 | } 32 | } 33 | return undefined; 34 | }; 35 | 36 | const init = function (...args) { 37 | const element = window().document.createElementNS(NS, "g"); 38 | element.setAttribute(str_class, str_arrow); 39 | const paths = ["line", str_tail, str_head].map(key => { 40 | const path = window().document.createElementNS(NS, str_path); 41 | path.setAttribute(str_class, `${str_arrow}-${key}`); 42 | element.appendChild(path); 43 | return path; 44 | }); 45 | paths[0].setAttribute(str_style, "fill:none;"); 46 | paths[1].setAttribute(str_stroke, str_none); 47 | paths[2].setAttribute(str_stroke, str_none); 48 | element.options = makeArrowOptions(); 49 | ArrowMethods.setPoints(element, ...args); 50 | const options = matchingOptions(...args); 51 | if (options) { 52 | Object.keys(options) 53 | .filter(key => ArrowMethods[key]) 54 | .forEach(key => ArrowMethods[key](element, options[key])); 55 | } 56 | return element; 57 | }; 58 | 59 | export default init; 60 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/makeArrowPaths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import * as S from "../../../environment/strings.js"; 5 | import { 6 | svg_add2, 7 | svg_sub2, 8 | svg_scale2, 9 | svg_magnitude2, 10 | } from "../../../general/algebra.js"; 11 | 12 | const ends = [S.str_tail, S.str_head]; 13 | const stringifyPoint = p => p.join(","); 14 | const pointsToPath = (points) => "M" + points.map(pt => pt.join(",")).join("L") + "Z"; 15 | 16 | /** 17 | * @description 18 | * @param {{ 19 | * points: [number, number, number, number], 20 | * padding: number, 21 | * bend: number, 22 | * pinch: number, 23 | * }} options 24 | */ 25 | const makeArrowPaths = (options) => { 26 | // throughout, tail is 0, head is 1 27 | /** @type {[[number, number], [number, number]]} */ 28 | let pts = [ 29 | [options.points[0] || 0, options.points[1] || 0], 30 | [options.points[2] || 0, options.points[3] || 0], 31 | ]; 32 | let vector = svg_sub2(pts[1], pts[0]); 33 | let midpoint = svg_add2(pts[0], svg_scale2(vector, 0.5)); 34 | // make sure arrow isn't too small 35 | const len = svg_magnitude2(vector); 36 | const minLength = ends 37 | .map(s => (options[s].visible 38 | ? (1 + options[s].padding) * options[s].height * 2.5 39 | : 0)) 40 | .reduce((a, b) => a + b, 0); 41 | if (len < minLength) { 42 | // check len === exactly 0. don't compare to epsilon here 43 | /** @type {[number, number]} */ 44 | const minVec = len === 0 ? [minLength, 0] : svg_scale2(vector, minLength / len); 45 | // pts = [svg_sub2, svg_add2].map(f => f(midpoint, svg_scale2(minVec, 0.5))); 46 | pts = [ 47 | svg_sub2(midpoint, svg_scale2(minVec, 0.5)), 48 | svg_add2(midpoint, svg_scale2(minVec, 0.5)), 49 | ]; 50 | vector = svg_sub2(pts[1], pts[0]); 51 | } else { 52 | // allow padding to be able to be applied. but still cap it at minLength 53 | // if (options.padding) {} 54 | } 55 | /** @type {[number, number]} */ 56 | let perpendicular = [vector[1], -vector[0]]; 57 | let bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); 58 | const bezs = pts.map(pt => svg_sub2(bezPoint, pt)); 59 | const bezsLen = bezs.map(v => svg_magnitude2(v)); 60 | const bezsNorm = bezs.map((bez, i) => (bezsLen[i] === 0 61 | ? bez 62 | : svg_scale2(bez, 1 / bezsLen[i]))); 63 | const vectors = bezsNorm.map(norm => svg_scale2(norm, -1)); 64 | /** @type {[[number, number], [number, number]]} */ 65 | const normals = [ 66 | [vectors[0][1], -vectors[0][0]], 67 | [vectors[1][1], -vectors[1][0]], 68 | ]; 69 | // get padding from either head/tail options or root of options 70 | const pad = ends.map((s, i) => (options[s].padding 71 | ? options[s].padding 72 | : (options.padding ? options.padding : 0.0))); 73 | const scales = ends 74 | .map((s, i) => options[s].height * (options[s].visible ? 1 : 0)) 75 | .map((n, i) => n + pad[i]); 76 | // .map((s, i) => options[s].height * ((options[s].visible ? 1 : 0) + pad[i])); 77 | const arcs = pts.map((pt, i) => svg_add2(pt, svg_scale2(bezsNorm[i], scales[i]))); 78 | // readjust bezier curve now that the arrow heads push inwards 79 | vector = svg_sub2(arcs[1], arcs[0]); 80 | perpendicular = [vector[1], -vector[0]]; 81 | midpoint = svg_add2(arcs[0], svg_scale2(vector, 0.5)); 82 | bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); 83 | // done adjust 84 | const controls = arcs 85 | .map((arc, i) => svg_add2(arc, svg_scale2(svg_sub2(bezPoint, arc), options.pinch))) 86 | const polyPoints = ends.map((s, i) => [ 87 | svg_add2(arcs[i], svg_scale2(vectors[i], options[s].height)), 88 | svg_add2(arcs[i], svg_scale2(normals[i], options[s].width / 2)), 89 | svg_add2(arcs[i], svg_scale2(normals[i], -options[s].width / 2)), 90 | ]); 91 | return { 92 | line: `M${stringifyPoint(arcs[0])}C${stringifyPoint(controls[0])},${stringifyPoint(controls[1])},${stringifyPoint(arcs[1])}`, 93 | tail: pointsToPath(polyPoints[0]), 94 | head: pointsToPath(polyPoints[1]), 95 | }; 96 | }; 97 | 98 | export default makeArrowPaths; 99 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import * as S from "../../../environment/strings.js"; 5 | import { toCamel } from "../../../general/string.js"; 6 | import semiFlatten from "../../../arguments/semiFlattenArrays.js"; 7 | import makeCoordinates from "../../../arguments/makeCoordinates.js"; 8 | import makeArrowPaths from "./makeArrowPaths.js"; 9 | import TransformMethods from "../shared/transforms.js"; 10 | 11 | // end is "head" or "tail" 12 | const setArrowheadOptions = (element, options, which) => { 13 | if (typeof options === S.str_boolean) { 14 | element.options[which].visible = options; 15 | } else if (typeof options === S.str_object) { 16 | Object.assign(element.options[which], options); 17 | if (options.visible == null) { 18 | element.options[which].visible = true; 19 | } 20 | } else if (options == null) { 21 | element.options[which].visible = true; 22 | } 23 | }; 24 | 25 | const setArrowStyle = (element, options = {}, which = S.str_head) => { 26 | const path = element.getElementsByClassName(`${S.str_arrow}-${which}`)[0]; 27 | // find options which translate to object methods (el.stroke("red")) 28 | Object.keys(options) 29 | .map(key => ({ key, fn: path[toCamel(key)] })) 30 | .filter(el => typeof el.fn === S.str_function && el.key !== "class") 31 | .forEach(el => el.fn(options[el.key])); 32 | // find options which don't work as methods, set as attributes 33 | // Object.keys(options) 34 | // .map(key => ({ key, fn: path[toCamel(key)] })) 35 | // .filter(el => typeof el.fn !== S.str_function && el.key !== "class") 36 | // .forEach(el => path.setAttribute(el.key, options[el.key])); 37 | // 38 | // apply a class attribute (add, don't overwrite existing classes) 39 | Object.keys(options) 40 | .filter(key => key === "class") 41 | .forEach(key => path.classList.add(options[key])); 42 | }; 43 | 44 | const redraw = (element) => { 45 | const paths = makeArrowPaths(element.options); 46 | Object.keys(paths) 47 | .map(path => ({ 48 | path, 49 | element: element.getElementsByClassName(`${S.str_arrow}-${path}`)[0], 50 | })) 51 | .filter(el => el.element) 52 | .map(el => { el.element.setAttribute("d", paths[el.path]); return el; }) 53 | .filter(el => element.options[el.path]) 54 | .forEach(el => el.element.setAttribute( 55 | "visibility", 56 | element.options[el.path].visible 57 | ? "visible" 58 | : "hidden", 59 | )); 60 | return element; 61 | }; 62 | 63 | const setPoints = (element, ...args) => { 64 | element.options.points = makeCoordinates(...semiFlatten(...args)).slice(0, 4); 65 | return redraw(element); 66 | }; 67 | 68 | const bend = (element, amount) => { 69 | element.options.bend = amount; 70 | return redraw(element); 71 | }; 72 | 73 | const pinch = (element, amount) => { 74 | element.options.pinch = amount; 75 | return redraw(element); 76 | }; 77 | 78 | const padding = (element, amount) => { 79 | element.options.padding = amount; 80 | return redraw(element); 81 | }; 82 | 83 | const head = (element, options) => { 84 | setArrowheadOptions(element, options, S.str_head); 85 | setArrowStyle(element, options, S.str_head); 86 | return redraw(element); 87 | }; 88 | 89 | const tail = (element, options) => { 90 | setArrowheadOptions(element, options, S.str_tail); 91 | setArrowStyle(element, options, S.str_tail); 92 | return redraw(element); 93 | }; 94 | 95 | const getLine = element => element.getElementsByClassName(`${S.str_arrow}-line`)[0]; 96 | const getHead = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_head}`)[0]; 97 | const getTail = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_tail}`)[0]; 98 | 99 | export default { 100 | setPoints, 101 | points: setPoints, 102 | bend, 103 | pinch, 104 | padding, 105 | head, 106 | tail, 107 | getLine, 108 | getHead, 109 | getTail, 110 | ...TransformMethods, 111 | }; 112 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | const endOptions = () => ({ 5 | visible: false, 6 | width: 8, 7 | height: 10, 8 | padding: 0.0, 9 | }); 10 | 11 | const makeArrowOptions = () => ({ 12 | head: endOptions(), 13 | tail: endOptions(), 14 | bend: 0.0, 15 | padding: 0.0, 16 | pinch: 0.618, 17 | points: [], 18 | }); 19 | 20 | export { 21 | makeArrowOptions, 22 | }; 23 | -------------------------------------------------------------------------------- /src/constructor/extensions/arrow/template.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | // const arrow = function (...args) { 5 | // const shape = window.document.createElementNS(svgNS, "g"); 6 | // const tailPoly = window.document.createElementNS(svgNS, "polygon"); 7 | // const headPoly = window.document.createElementNS(svgNS, "polygon"); 8 | // const arrowPath = window.document.createElementNS(svgNS, "path"); 9 | // tailPoly.setAttributeNS(null, "class", "svg-arrow-tail"); 10 | // headPoly.setAttributeNS(null, "class", "svg-arrow-head"); 11 | // arrowPath.setAttributeNS(null, "class", "svg-arrow-path"); 12 | // tailPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;"); 13 | // headPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;"); 14 | // arrowPath.setAttributeNS(null, "style", "fill: none;"); 15 | // shape.appendChild(arrowPath); 16 | // shape.appendChild(tailPoly); 17 | // shape.appendChild(headPoly); 18 | // shape.options = { 19 | // head: { width: 0.5, height: 2, visible: false, padding: 0.0 }, 20 | // tail: { width: 0.5, height: 2, visible: false, padding: 0.0 }, 21 | // curve: 0.0, 22 | // pinch: 0.618, 23 | // points: [], 24 | // }; 25 | // setArrowPoints(shape, ...args); 26 | // prepare("arrow", shape); 27 | // shape.setPoints = (...a) => setArrowPoints(shape, ...a); 28 | // return shape; 29 | // }; 30 | -------------------------------------------------------------------------------- /src/constructor/extensions/circle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 5 | import nodes_attributes from "../../spec/nodes_attributes.js"; 6 | import { svg_distance2 } from "../../general/algebra.js"; 7 | import TransformMethods from "./shared/transforms.js"; 8 | import URLMethods from "./shared/urls.js"; 9 | import * as DOM from "./shared/dom.js"; 10 | 11 | const setRadius = (el, r) => { 12 | el.setAttribute(nodes_attributes.circle[2], r); 13 | return el; 14 | }; 15 | 16 | const setOrigin = (el, a, b) => { 17 | [...makeCoordinates(...[a, b].flat()).slice(0, 2)] 18 | .forEach((value, i) => el.setAttribute(nodes_attributes.circle[i], value)); 19 | return el; 20 | }; 21 | 22 | const fromPoints = (a, b, c, d) => [a, b, svg_distance2([a, b], [c, d])]; 23 | /** 24 | * @name circle 25 | * @memberof svg 26 | * @description Draw an SVG Circle element. 27 | * @param {number} radius the radius of the circle 28 | * @param {...number|number[]} center the center of the circle 29 | * @returns {Element} an SVG node element 30 | * @linkcode SVG ./src/nodes/spec/circle.js 28 31 | */ 32 | export default { 33 | circle: { 34 | args: (a, b, c, d) => { 35 | const coords = makeCoordinates(...[a, b, c, d].flat()); 36 | // console.log("SVG circle coords", coords); 37 | switch (coords.length) { 38 | case 0: case 1: return [, , ...coords]; 39 | case 2: case 3: return coords; 40 | // case 4 41 | default: return fromPoints(...coords); 42 | } 43 | // return makeCoordinates(...flatten(a, b, c)).slice(0, 3); 44 | }, 45 | methods: { 46 | radius: setRadius, 47 | setRadius, 48 | origin: setOrigin, 49 | setOrigin, 50 | center: setOrigin, 51 | setCenter: setOrigin, 52 | position: setOrigin, 53 | setPosition: setOrigin, 54 | ...TransformMethods, 55 | ...URLMethods, 56 | ...DOM, 57 | }, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/constructor/extensions/curve/arguments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "../../../arguments/makeCoordinates.js"; 5 | import makeCurvePath from "./makeCurvePath.js"; 6 | 7 | const curveArguments = (...args) => [ 8 | makeCurvePath(makeCoordinates(...args.flat())), 9 | ]; 10 | 11 | export default curveArguments; 12 | -------------------------------------------------------------------------------- /src/constructor/extensions/curve/getCurveEndpoints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | const getNumbersFromPathCommand = str => str 5 | .slice(1) 6 | .split(/[, ]+/) 7 | .map(s => parseFloat(s)); 8 | 9 | // this gets the parameter numbers, in an array 10 | const getCurveTos = d => d 11 | .match(/[Cc][(0-9), .-]+/) 12 | .map(curve => getNumbersFromPathCommand(curve)); 13 | 14 | const getMoveTos = d => d 15 | .match(/[Mm][(0-9), .-]+/) 16 | .map(curve => getNumbersFromPathCommand(curve)); 17 | 18 | const getCurveEndpoints = (d) => { 19 | // get only the first Move and Curve commands 20 | const move = getMoveTos(d).shift(); 21 | const curve = getCurveTos(d).shift(); 22 | const start = move 23 | ? [move[move.length - 2], move[move.length - 1]] 24 | : [0, 0]; 25 | const end = curve 26 | ? [curve[curve.length - 2], curve[curve.length - 1]] 27 | : [0, 0]; 28 | return [...start, ...end]; 29 | }; 30 | 31 | export default getCurveEndpoints; 32 | -------------------------------------------------------------------------------- /src/constructor/extensions/curve/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import args from "./arguments.js"; 5 | import curve_methods from "./methods.js"; 6 | import { str_path } from "../../../environment/strings.js"; 7 | 8 | export default { 9 | curve: { 10 | nodeName: str_path, 11 | attributes: ["d"], 12 | args, // one function 13 | methods: curve_methods, // object of functions 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/constructor/extensions/curve/makeCurvePath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { svg_add2, svg_sub2, svg_scale2 } from "../../../general/algebra.js"; 5 | 6 | // endpoints is an array of 4 numbers 7 | const makeCurvePath = (endpoints = [], bend = 0, pinch = 0.5) => { 8 | /** @type {[number, number]} */ 9 | const tailPt = [endpoints[0] || 0, endpoints[1] || 0]; 10 | /** @type {[number, number]} */ 11 | const headPt = [endpoints[2] || 0, endpoints[3] || 0]; 12 | const vector = svg_sub2(headPt, tailPt); 13 | const midpoint = svg_add2(tailPt, svg_scale2(vector, 0.5)); 14 | /** @type {[number, number]} */ 15 | const perpendicular = [vector[1], -vector[0]]; 16 | const bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, bend)); 17 | const tailControl = svg_add2(tailPt, svg_scale2(svg_sub2(bezPoint, tailPt), pinch)); 18 | const headControl = svg_add2(headPt, svg_scale2(svg_sub2(bezPoint, headPt), pinch)); 19 | return `M${tailPt[0]},${tailPt[1]}C${tailControl[0]},${tailControl[1]} ${headControl[0]},${headControl[1]} ${headPt[0]},${headPt[1]}`; 20 | }; 21 | 22 | export default makeCurvePath; 23 | -------------------------------------------------------------------------------- /src/constructor/extensions/curve/methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "../../../arguments/makeCoordinates.js"; 5 | import makeCurvePath from "./makeCurvePath.js"; 6 | import getCurveEndpoints from "./getCurveEndpoints.js"; 7 | import TransformMethods from "../shared/transforms.js"; 8 | 9 | const setPoints = (element, ...args) => { 10 | const coords = makeCoordinates(...args.flat()).slice(0, 4); 11 | element.setAttribute("d", makeCurvePath(coords, element._bend, element._pinch)); 12 | return element; 13 | }; 14 | 15 | const bend = (element, amount) => { 16 | element._bend = amount; 17 | return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); 18 | }; 19 | 20 | const pinch = (element, amount) => { 21 | element._pinch = amount; 22 | return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); 23 | }; 24 | 25 | export default { 26 | setPoints, 27 | bend, 28 | pinch, 29 | ...TransformMethods, 30 | }; 31 | -------------------------------------------------------------------------------- /src/constructor/extensions/ellipse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 5 | import nodes_attributes from "../../spec/nodes_attributes.js"; 6 | import TransformMethods from "./shared/transforms.js"; 7 | import URLMethods from "./shared/urls.js"; 8 | import * as DOM from "./shared/dom.js"; 9 | 10 | // const setRadii = (el, rx, ry) => [,,rx,ry] 11 | // .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); 12 | const setRadii = (el, rx, ry) => { 13 | [, , rx, ry].forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); 14 | return el; 15 | }; 16 | 17 | const setOrigin = (el, a, b) => { 18 | [...makeCoordinates(...[a, b].flat()).slice(0, 2)] 19 | .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); 20 | return el; 21 | }; 22 | 23 | export default { 24 | ellipse: { 25 | args: (a, b, c, d) => { 26 | const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); 27 | switch (coords.length) { 28 | case 0: case 1: case 2: return [, , ...coords]; 29 | default: return coords; 30 | } 31 | }, 32 | methods: { 33 | radius: setRadii, 34 | setRadius: setRadii, 35 | origin: setOrigin, 36 | setOrigin, 37 | center: setOrigin, 38 | setCenter: setOrigin, 39 | position: setOrigin, 40 | setPosition: setOrigin, 41 | ...TransformMethods, 42 | ...URLMethods, 43 | ...DOM, 44 | }, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/constructor/extensions/g.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import svgNS from "../../spec/namespace.js"; 5 | import window from "../../environment/window.js"; 6 | // import * as S from "../../environment/strings.js"; 7 | // import { sync } from "../../file/load.js"; 8 | // import { moveChildren } from "../../methods/dom.js"; 9 | import TransformMethods from "./shared/transforms.js"; 10 | import URLMethods from "./shared/urls.js"; 11 | import * as DOM from "./shared/dom.js"; 12 | 13 | const loadGroup = (group, ...sources) => { 14 | // const elements = sources.map(source => sync(source)) 15 | // .filter(a => a !== undefined); 16 | // elements.filter(element => element.tagName === S.str_svg) 17 | // .forEach(element => moveChildren(group, element)); 18 | // elements.filter(element => element.tagName !== S.str_svg) 19 | // .forEach(element => group.appendChild(element)); 20 | return group; 21 | }; 22 | 23 | const init = (...sources) => { 24 | const group = window().document.createElementNS(svgNS, "g"); 25 | return loadGroup(group, ...sources); 26 | }; 27 | 28 | export default { 29 | g: { 30 | // init, 31 | methods: { 32 | // load: loadGroup, 33 | ...TransformMethods, 34 | ...URLMethods, 35 | ...DOM, 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/constructor/extensions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import svgDef from "./svg/index.js"; 5 | import gDef from "./g.js"; 6 | import circleDef from "./circle.js"; 7 | import ellipseDef from "./ellipse.js"; 8 | import lineDef from "./line.js"; 9 | import pathDef from "./path.js"; 10 | import rectDef from "./rect.js"; 11 | import styleDef from "./style.js"; 12 | import textDef from "./text.js"; 13 | // multiple nodes in one 14 | import maskTypes from "./maskTypes.js"; 15 | import polyDefs from "./polys.js"; 16 | // extensions 17 | import arcDef from "./arc/index.js"; 18 | import arrowDef from "./arrow/index.js"; 19 | import curveDef from "./curve/index.js"; 20 | import wedgeDef from "./wedge/index.js"; 21 | import origamiDef from "./origami/index.js"; 22 | /** 23 | * in each of these instances, arguments maps the arguments to attributes 24 | * as the attributes are listed in the "attributes" folder. 25 | * 26 | * arguments: function. this should convert the array of arguments into 27 | * an array of (processed) arguments. 1:1. arguments into arguments. 28 | * make sure it is returning an array. 29 | * 30 | */ 31 | export default { 32 | ...svgDef, 33 | ...gDef, 34 | ...circleDef, 35 | ...ellipseDef, 36 | ...lineDef, 37 | ...pathDef, 38 | ...rectDef, 39 | ...styleDef, 40 | ...textDef, 41 | // multiple 42 | ...maskTypes, 43 | ...polyDefs, 44 | // extensions 45 | ...arcDef, 46 | ...arrowDef, 47 | ...curveDef, 48 | ...wedgeDef, 49 | ...origamiDef, 50 | }; 51 | -------------------------------------------------------------------------------- /src/constructor/extensions/line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import semiFlattenArrays from "../../arguments/semiFlattenArrays.js"; 5 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 6 | import nodes_attributes from "../../spec/nodes_attributes.js"; 7 | import TransformMethods from "./shared/transforms.js"; 8 | import URLMethods from "./shared/urls.js"; 9 | import * as DOM from "./shared/dom.js"; 10 | 11 | const Args = (...args) => makeCoordinates(...semiFlattenArrays(...args)).slice(0, 4); 12 | 13 | const setPoints = (element, ...args) => { 14 | Args(...args).forEach((value, i) => element.setAttribute(nodes_attributes.line[i], value)); 15 | return element; 16 | }; 17 | /** 18 | * @name line 19 | * @description SVG Line element 20 | * @memberof SVG 21 | * @linkcode SVG ./src/nodes/spec/line.js 18 22 | */ 23 | export default { 24 | line: { 25 | args: Args, 26 | methods: { 27 | setPoints, 28 | ...TransformMethods, 29 | ...URLMethods, 30 | ...DOM, 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/constructor/extensions/maskTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import * as S from "../../environment/strings.js"; 5 | import { makeUUID } from "../../general/string.js"; 6 | import { setViewBox } from "../../general/viewBox.js"; 7 | import TransformMethods from "./shared/transforms.js"; 8 | import URLMethods from "./shared/urls.js"; 9 | import * as DOM from "./shared/dom.js"; 10 | 11 | const makeIDString = function () { 12 | return Array.from(arguments) 13 | .filter(a => typeof a === S.str_string || a instanceof String) 14 | .shift() || makeUUID(); 15 | }; 16 | 17 | const maskArgs = (...args) => [makeIDString(...args)]; 18 | 19 | export default { 20 | mask: { 21 | args: maskArgs, 22 | methods: { 23 | ...TransformMethods, 24 | ...URLMethods, 25 | ...DOM, 26 | }, 27 | }, 28 | clipPath: { 29 | args: maskArgs, 30 | methods: { 31 | ...TransformMethods, 32 | ...URLMethods, 33 | ...DOM, 34 | }, 35 | }, 36 | symbol: { 37 | args: maskArgs, 38 | methods: { 39 | ...TransformMethods, 40 | ...URLMethods, 41 | ...DOM, 42 | }, 43 | }, 44 | marker: { 45 | args: maskArgs, 46 | methods: { 47 | size: setViewBox, 48 | setViewBox: setViewBox, 49 | ...TransformMethods, 50 | ...URLMethods, 51 | ...DOM, 52 | }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /src/constructor/extensions/origami/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import init from "./init.js"; 5 | import methods from "./methods.js"; 6 | 7 | export default { 8 | origami: { 9 | nodeName: "g", 10 | init, 11 | args: () => [], 12 | methods, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/constructor/extensions/origami/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import NS from "../../../spec/namespace.js"; 5 | import window from "../../../environment/window.js"; 6 | import lib from "../../../environment/lib.js"; 7 | 8 | const init = (graph, ...args) => { 9 | const g = window().document.createElementNS(NS, "g"); 10 | lib.ear.convert.foldToSvg.render(graph, g, ...args); 11 | return g; 12 | }; 13 | 14 | export default init; 15 | -------------------------------------------------------------------------------- /src/constructor/extensions/origami/methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | // import window from "../../../environment/window.js"; 5 | // import NS from "../../../spec/namespace.js"; 6 | // import lib from "../../../environment/lib.js"; 7 | import TransformMethods from "../shared/transforms.js"; 8 | import URLMethods from "../shared/urls.js"; 9 | import * as DOM from "../shared/dom.js"; 10 | 11 | // const clearSVG = (element) => { 12 | // Array.from(element.attributes) 13 | // .filter(attr => attr.name !== "xmlns" && attr.name !== "version") 14 | // .forEach(attr => element.removeAttribute(attr.name)); 15 | // return DOM.removeChildren(element); 16 | // }; 17 | 18 | // const vertices = (...args) => { 19 | // lib.ear.convert.foldToSvg.vertices(...args); 20 | // const g = window().document.createElementNS(NS, "g"); 21 | // lib.ear.convert.foldToSvg.drawInto(g, ...args); 22 | // return g; 23 | // }; 24 | 25 | // const edges = (...args) => { 26 | // console.log("edges"); 27 | // }; 28 | 29 | // const faces = (...args) => { 30 | // console.log("faces"); 31 | // }; 32 | 33 | // these will end up as methods on the nodes 34 | export default { 35 | // vertices, 36 | // edges, 37 | // faces, 38 | ...TransformMethods, 39 | ...URLMethods, 40 | ...DOM, 41 | }; 42 | -------------------------------------------------------------------------------- /src/constructor/extensions/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | pathCommandNames, 6 | parsePathCommands, 7 | } from "../../general/path.js"; 8 | import TransformMethods from "./shared/transforms.js"; 9 | import URLMethods from "./shared/urls.js"; 10 | import * as DOM from "./shared/dom.js"; 11 | /** 12 | * @param {SVGElement} el one svg element, intended to be a element 13 | * @returns {string} the "d" attribute, or if unset, returns an empty string "". 14 | */ 15 | const getD = (el) => { 16 | const attr = el.getAttribute("d"); 17 | return (attr == null) ? "" : attr; 18 | }; 19 | 20 | const clear = element => { 21 | element.removeAttribute("d"); 22 | return element; 23 | }; 24 | 25 | // todo: would be great if for arguments > 2 it alternated space and comma 26 | const appendPathCommand = (el, command, ...args) => { 27 | el.setAttribute("d", `${getD(el)}${command}${args.flat().join(" ")}`); 28 | return el; 29 | }; 30 | 31 | const appendPathString = (el, string) => { 32 | el.setAttribute("d", `${getD(el)}${string}`); 33 | return el; 34 | }; 35 | 36 | // user got the commands object and is returning it to us 37 | // const setPathCommands = (element, commandsObject) => commandsObject 38 | // .forEach(el => appendPathCommand(element, el.command, el.values)); 39 | 40 | // user provided one already-formatted path string 41 | const setPathString = (element, commandsString) => { 42 | element.setAttribute("d", commandsString); 43 | return element; 44 | }; 45 | 46 | // break out the path commands into an array of descriptive objects 47 | const getCommands = element => parsePathCommands(getD(element)); 48 | 49 | // const setters = { 50 | // string: setPathString, 51 | // object: setPathCommands, 52 | // }; 53 | // const appenders = { 54 | // string: appendPathString, 55 | // object: appendPathCommands, 56 | // }; 57 | 58 | // depending on the user's argument, different setters will get called 59 | // const noClearSet = (element, ...args) => { 60 | // if (args.length === 1) { 61 | // const typ = typeof args[0]; 62 | // if (setters[typ]) { 63 | // setters[typ](element, args[0]); 64 | // } 65 | // } 66 | // }; 67 | 68 | // const clearAndSet = (element, ...args) => { 69 | // if (args.length === 1) { 70 | // const typ = typeof args[0]; 71 | // if (setters[typ]) { 72 | // clear(element); 73 | // setters[typ](element, args[0]); 74 | // } 75 | // } 76 | // }; 77 | 78 | const path_methods = { 79 | addCommand: appendPathCommand, 80 | appendCommand: appendPathCommand, 81 | clear, 82 | getCommands: getCommands, 83 | get: getCommands, 84 | getD: el => el.getAttribute("d"), 85 | // set: clearAndSet, 86 | // add: noClearSet, 87 | ...TransformMethods, 88 | ...URLMethods, 89 | ...DOM, 90 | }; 91 | 92 | Object.keys(pathCommandNames).forEach((key) => { 93 | path_methods[pathCommandNames[key]] = (el, ...args) => appendPathCommand(el, key, ...args); 94 | }); 95 | 96 | export default { 97 | path: { 98 | methods: path_methods, 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /src/constructor/extensions/polys.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import * as S from "../../environment/strings.js"; 5 | import semiFlattenArrays from "../../arguments/semiFlattenArrays.js"; 6 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 7 | import TransformMethods from "./shared/transforms.js"; 8 | import URLMethods from "./shared/urls.js"; 9 | import * as DOM from "./shared/dom.js"; 10 | 11 | const getPoints = (el) => { 12 | const attr = el.getAttribute(S.str_points); 13 | return (attr == null) ? "" : attr; 14 | }; 15 | 16 | const polyString = function () { 17 | return Array 18 | .from(Array(Math.floor(arguments.length / 2))) 19 | .map((_, i) => `${arguments[i * 2 + 0]},${arguments[i * 2 + 1]}`) 20 | .join(" "); 21 | }; 22 | 23 | const stringifyArgs = (...args) => [ 24 | polyString(...makeCoordinates(...semiFlattenArrays(...args))), 25 | ]; 26 | 27 | const setPoints = (element, ...args) => { 28 | element.setAttribute(S.str_points, stringifyArgs(...args)[0]); 29 | return element; 30 | }; 31 | 32 | const addPoint = (element, ...args) => { 33 | element.setAttribute(S.str_points, [getPoints(element), stringifyArgs(...args)[0]] 34 | .filter(a => a !== "") 35 | .join(" ")); 36 | return element; 37 | }; 38 | 39 | // this should be improved 40 | // right now the special case is if there is only 1 argument and it's a string 41 | // it should be able to take strings or numbers at any point, 42 | // converting the strings to coordinates 43 | const Args = function (...args) { 44 | return args.length === 1 && typeof args[0] === S.str_string 45 | ? [args[0]] 46 | : stringifyArgs(...args); 47 | }; 48 | 49 | export default { 50 | polyline: { 51 | args: Args, 52 | methods: { 53 | setPoints, 54 | addPoint, 55 | ...TransformMethods, 56 | ...URLMethods, 57 | ...DOM, 58 | }, 59 | }, 60 | polygon: { 61 | args: Args, 62 | methods: { 63 | setPoints, 64 | addPoint, 65 | ...TransformMethods, 66 | ...URLMethods, 67 | ...DOM, 68 | }, 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/constructor/extensions/rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 5 | import nodes_attributes from "../../spec/nodes_attributes.js"; 6 | import TransformMethods from "./shared/transforms.js"; 7 | import URLMethods from "./shared/urls.js"; 8 | import * as DOM from "./shared/dom.js"; 9 | 10 | const setRectSize = (el, rx, ry) => { 11 | [, , rx, ry] 12 | .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); 13 | return el; 14 | }; 15 | 16 | const setRectOrigin = (el, a, b) => { 17 | [...makeCoordinates(...[a, b].flat()).slice(0, 2)] 18 | .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); 19 | return el; 20 | }; 21 | 22 | // can handle negative widths and heights 23 | const fixNegatives = function (arr) { 24 | [0, 1].forEach(i => { 25 | if (arr[2 + i] < 0) { 26 | if (arr[0 + i] === undefined) { arr[0 + i] = 0; } 27 | arr[0 + i] += arr[2 + i]; 28 | arr[2 + i] = -arr[2 + i]; 29 | } 30 | }); 31 | return arr; 32 | }; 33 | /** 34 | * @name rect 35 | * @memberof svg 36 | * @description Draw an SVG Rect element. 37 | * @param {number} x the x coordinate of the corner 38 | * @param {number} y the y coordinate of the corner 39 | * @param {number} width the length along the x dimension 40 | * @param {number} height the length along the y dimension 41 | * @returns {Element} an SVG node element 42 | * @linkcode SVG ./src/nodes/spec/rect.js 40 43 | */ 44 | export default { 45 | rect: { 46 | args: (a, b, c, d) => { 47 | const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); 48 | switch (coords.length) { 49 | case 0: case 1: case 2: case 3: return fixNegatives([, , ...coords]); 50 | default: return fixNegatives(coords); 51 | } 52 | }, 53 | methods: { 54 | origin: setRectOrigin, 55 | setOrigin: setRectOrigin, 56 | center: setRectOrigin, 57 | setCenter: setRectOrigin, 58 | size: setRectSize, 59 | setSize: setRectSize, 60 | ...TransformMethods, 61 | ...URLMethods, 62 | ...DOM, 63 | }, 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /src/constructor/extensions/shared/dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { toKebab } from "../../../general/string.js"; 5 | 6 | export const removeChildren = (element) => { 7 | while (element.lastChild) { 8 | element.removeChild(element.lastChild); 9 | } 10 | return element; 11 | }; 12 | 13 | export const appendTo = (element, parent) => { 14 | if (parent && parent.appendChild) { 15 | parent.appendChild(element); 16 | } 17 | return element; 18 | }; 19 | 20 | export const setAttributes = (element, attrs) => { 21 | Object.keys(attrs) 22 | .forEach(key => element.setAttribute(toKebab(key), attrs[key])); 23 | return element; 24 | }; 25 | -------------------------------------------------------------------------------- /src/constructor/extensions/shared/makeArcPath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { svg_polar_to_cart } from "../../../general/algebra.js"; 5 | 6 | const arcPath = (x, y, radius, startAngle, endAngle, includeCenter = false) => { 7 | if (endAngle == null) { return ""; } 8 | const start = svg_polar_to_cart(startAngle, radius); 9 | const end = svg_polar_to_cart(endAngle, radius); 10 | const arcVec = [end[0] - start[0], end[1] - start[1]]; 11 | const py = start[0] * end[1] - start[1] * end[0]; 12 | const px = start[0] * end[0] + start[1] * end[1]; 13 | const arcdir = (Math.atan2(py, px) > 0 ? 0 : 1); 14 | let d = (includeCenter 15 | ? `M ${x},${y} l ${start[0]},${start[1]} ` 16 | : `M ${x + start[0]},${y + start[1]} `); 17 | d += ["a ", radius, radius, 0, arcdir, 1, arcVec[0], arcVec[1]].join(" "); 18 | if (includeCenter) { d += " Z"; } 19 | return d; 20 | }; 21 | 22 | export default arcPath; 23 | -------------------------------------------------------------------------------- /src/constructor/extensions/shared/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { str_transform } from "../../../environment/strings.js"; 5 | 6 | const getAttr = (element) => { 7 | const t = element.getAttribute(str_transform); 8 | return (t == null || t === "") ? undefined : t; 9 | }; 10 | 11 | const TransformMethods = { 12 | clearTransform: (el) => { el.removeAttribute(str_transform); return el; }, 13 | }; 14 | 15 | ["translate", "rotate", "scale", "matrix"].forEach(key => { 16 | TransformMethods[key] = (element, ...args) => { 17 | element.setAttribute( 18 | str_transform, 19 | [getAttr(element), `${key}(${args.join(" ")})`] 20 | .filter(a => a !== undefined) 21 | .join(" "), 22 | ); 23 | return element; 24 | }; 25 | }); 26 | 27 | export default TransformMethods; 28 | -------------------------------------------------------------------------------- /src/constructor/extensions/shared/urls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | str_string, 6 | str_id, 7 | } from "../../../environment/strings.js"; 8 | import { toCamel } from "../../../general/string.js"; 9 | 10 | // for the clip-path and mask values. looks for the ID as a "url(#id-name)" string 11 | const findIdURL = function (arg) { 12 | if (arg == null) { return ""; } 13 | if (typeof arg === str_string) { 14 | return arg.slice(0, 3) === "url" 15 | ? arg 16 | : `url(#${arg})`; 17 | } 18 | if (arg.getAttribute != null) { 19 | const idString = arg.getAttribute(str_id); 20 | return `url(#${idString})`; 21 | } 22 | return ""; 23 | }; 24 | 25 | const methods = {}; 26 | 27 | // these do not represent the nodes that these methods are applied to 28 | // every node gets these attribute-setting method (pointing to a mask) 29 | ["clip-path", 30 | "mask", 31 | "symbol", 32 | "marker-end", 33 | "marker-mid", 34 | "marker-start", 35 | ].forEach(attr => { 36 | methods[toCamel(attr)] = (element, parent) => { 37 | element.setAttribute(attr, findIdURL(parent)); 38 | return element; 39 | }; 40 | }); 41 | 42 | export default methods; 43 | -------------------------------------------------------------------------------- /src/constructor/extensions/style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import svgNS from "../../spec/namespace.js"; 5 | import window from "../../environment/window.js"; 6 | import { makeCDATASection } from "../../general/cdata.js"; 7 | 8 | export default { 9 | style: { 10 | init: (text) => { 11 | const el = window().document.createElementNS(svgNS, "style"); 12 | el.setAttribute("type", "text/css"); 13 | el.textContent = ""; 14 | el.appendChild(makeCDATASection(text)); 15 | return el; 16 | }, 17 | methods: { 18 | setTextContent: (el, text) => { 19 | el.textContent = ""; 20 | el.appendChild(makeCDATASection(text)); 21 | return el; 22 | }, 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/animation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import window from "../../../environment/window.js"; 5 | import { makeUUID } from "../../../general/string.js"; 6 | 7 | const Animation = function (element) { 8 | let start; 9 | let frame = 0; 10 | let requestId; 11 | const handlers = {}; 12 | 13 | const stop = () => { 14 | if (window().cancelAnimationFrame) { 15 | window().cancelAnimationFrame(requestId); 16 | } 17 | Object.keys(handlers).forEach(uuid => delete handlers[uuid]); 18 | }; 19 | 20 | const play = (handler) => { 21 | stop(); 22 | if (!handler || !(window().requestAnimationFrame)) { return; } 23 | start = performance.now(); 24 | frame = 0; 25 | const uuid = makeUUID(); 26 | handlers[uuid] = (now) => { 27 | const time = (now - start) * 1e-3; 28 | handler({ time, frame }); 29 | frame += 1; 30 | if (handlers[uuid]) { 31 | requestId = window().requestAnimationFrame(handlers[uuid]); 32 | } 33 | }; 34 | requestId = window().requestAnimationFrame(handlers[uuid]); 35 | }; 36 | 37 | Object.defineProperty(element, "play", { set: play, enumerable: true }); 38 | Object.defineProperty(element, "stop", { value: stop, enumerable: true }); 39 | }; 40 | 41 | export default Animation; 42 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/getSVGFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { 5 | str_function, 6 | } from "../../../environment/strings.js"; 7 | import { getViewBox } from "../../../general/viewBox.js"; 8 | 9 | const getSVGFrame = function (element) { 10 | const viewBox = getViewBox(element); 11 | if (viewBox !== undefined) { 12 | return viewBox; 13 | } 14 | if (typeof element.getBoundingClientRect === str_function) { 15 | const rr = element.getBoundingClientRect(); 16 | return [rr.x, rr.y, rr.width, rr.height]; 17 | } 18 | return []; 19 | }; 20 | 21 | export default getSVGFrame; 22 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import svgNS from "../../../spec/namespace.js"; 5 | import window from "../../../environment/window.js"; 6 | import makeViewBox from "../../../arguments/makeViewBox.js"; 7 | import makeCoordinates from "../../../arguments/makeCoordinates.js"; 8 | import methods from "./methods.js"; 9 | // import methods, { loadSVG } from "./methods.js"; 10 | import Touch from "./touch.js"; 11 | import Animation from "./animation.js"; 12 | import Controls from "./controls.js"; 13 | 14 | export default { 15 | svg: { 16 | args: (...args) => [makeViewBox(makeCoordinates(...args))].filter(a => a != null), 17 | methods, 18 | init: (...args) => { 19 | const element = window().document.createElementNS(svgNS, "svg"); 20 | element.setAttribute("version", "1.1"); 21 | element.setAttribute("xmlns", svgNS); 22 | // args.filter(a => typeof a === str_string) 23 | // .forEach(string => loadSVG(element, string)); 24 | args.filter(a => a != null) 25 | .filter(el => el.appendChild) 26 | .forEach(parent => parent.appendChild(element)); 27 | Touch(element); 28 | Animation(element); 29 | Controls(element); 30 | return element; 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/makeBackground.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import window from "../../../environment/window.js"; 5 | import { 6 | str_class, 7 | str_stroke, 8 | str_none, 9 | str_fill, 10 | } from "../../../environment/strings.js"; 11 | import NS from "../../../spec/namespace.js"; 12 | import nodes_attributes from "../../../spec/nodes_attributes.js"; 13 | import getSVGFrame from "./getSVGFrame.js"; 14 | 15 | const bgClass = "svg-background-rectangle"; 16 | 17 | // i prevented circular dependency by passing a pointer to Constructor through 'this' 18 | // every function is bound 19 | const makeBackground = function (element, color) { 20 | let backRect = Array.from(element.childNodes) 21 | .filter(child => child.getAttribute(str_class) === bgClass) 22 | .shift(); 23 | if (backRect == null) { 24 | backRect = window().document.createElementNS(NS, "rect"); 25 | getSVGFrame(element).forEach((n, i) => backRect.setAttribute(nodes_attributes.rect[i], n)); 26 | // backRect = this.Constructor("rect", null, ...getSVGFrame(element)); 27 | backRect.setAttribute(str_class, bgClass); 28 | backRect.setAttribute(str_stroke, str_none); 29 | element.insertBefore(backRect, element.firstChild); 30 | } 31 | backRect.setAttribute(str_fill, color); 32 | return element; 33 | }; 34 | 35 | export default makeBackground; 36 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import window from "../../../environment/window.js"; 5 | import { 6 | str_style, 7 | // str_function, 8 | } from "../../../environment/strings.js"; 9 | import NS from "../../../spec/namespace.js"; 10 | import { makeCDATASection } from "../../../general/cdata.js"; 11 | // import Load from "../../../file/load.js"; 12 | // import Save from "../../../file/save.js"; 13 | import { getViewBox, setViewBox } from "../../../general/viewBox.js"; 14 | import makeBackground from "./makeBackground.js"; 15 | import getSVGFrame from "./getSVGFrame.js"; 16 | import TransformMethods from "../shared/transforms.js"; 17 | import * as DOM from "../shared/dom.js"; 18 | 19 | // check if the loader is running synchronously or asynchronously 20 | // export const loadSVG = (target, data) => { 21 | // const result = Load(data); 22 | // if (result == null) { return; } 23 | // return (typeof result.then === str_function) 24 | // ? result.then(svg => assignSVG(target, svg)) 25 | // : assignSVG(target, result); 26 | // }; 27 | 28 | const setPadding = function (element, padding) { 29 | const viewBox = getViewBox(element); 30 | if (viewBox !== undefined) { 31 | setViewBox(element, ...[-padding, -padding, padding * 2, padding * 2] 32 | .map((nudge, i) => viewBox[i] + nudge)); 33 | } 34 | return element; 35 | }; 36 | /** 37 | * @description Locate the first instance of an element matching a nodeName 38 | */ 39 | export const findOneElement = function (element, nodeName) { 40 | const styles = element.getElementsByTagName(nodeName); 41 | return styles.length ? styles[0] : null; 42 | }; 43 | /** 44 | * @description Locate an existing stylesheet and append text to it, or 45 | * create a new stylesheet with the text contents. 46 | */ 47 | const stylesheet = function (element, textContent) { 48 | let styleSection = findOneElement(element, str_style); 49 | if (styleSection == null) { 50 | styleSection = window().document.createElementNS(NS, str_style); 51 | styleSection.setTextContent = (text) => { 52 | styleSection.textContent = ""; 53 | styleSection.appendChild(makeCDATASection(text)); 54 | return styleSection; 55 | }; 56 | element.insertBefore(styleSection, element.firstChild); 57 | } 58 | styleSection.textContent = ""; 59 | styleSection.appendChild(makeCDATASection(textContent)); 60 | return styleSection; 61 | }; 62 | 63 | const clearSVG = (element) => { 64 | Array.from(element.attributes) 65 | .filter(attr => attr.name !== "xmlns" && attr.name !== "version") 66 | .forEach(attr => element.removeAttribute(attr.name)); 67 | return DOM.removeChildren(element); 68 | }; 69 | 70 | // these will end up as methods on the nodes 71 | export default { 72 | clear: clearSVG, 73 | size: setViewBox, 74 | setViewBox, 75 | getViewBox, 76 | padding: setPadding, 77 | background: makeBackground, 78 | getWidth: el => getSVGFrame(el)[2], 79 | getHeight: el => getSVGFrame(el)[3], 80 | // this is named "stylesheet" because "style" property is already taken. 81 | stylesheet: function (el, text) { return stylesheet.call(this, el, text); }, 82 | // load: loadSVG, 83 | // save: Save, 84 | ...TransformMethods, 85 | ...DOM, 86 | }; 87 | 88 | // svg.load = function (element, data, callback) { 89 | // return Load(data, (svg, error) => { 90 | // if (svg != null) { replaceSVG(element, svg); } 91 | // if (callback != null) { callback(element, error); } 92 | // }); 93 | // }; 94 | -------------------------------------------------------------------------------- /src/constructor/extensions/svg/touch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { capitalized } from "../../../general/string.js"; 5 | import { convertToViewBox } from "../../../general/viewBox.js"; 6 | 7 | const eventNameCategories = { 8 | move: ["mousemove", "touchmove"], 9 | press: ["mousedown", "touchstart"], // "mouseover", 10 | release: ["mouseup", "touchend"], 11 | leave: ["mouseleave", "touchcancel"], 12 | }; 13 | 14 | const off = (el, handlers) => Object.values(eventNameCategories) 15 | .flat() 16 | .forEach((handlerName) => { 17 | handlers[handlerName].forEach(func => el 18 | .removeEventListener(handlerName, func)); 19 | handlers[handlerName] = []; 20 | }); 21 | 22 | const defineGetter = (obj, prop, value) => Object 23 | .defineProperty(obj, prop, { 24 | get: () => value, 25 | enumerable: true, 26 | configurable: true, 27 | }); 28 | 29 | // todo, more pointers for multiple screen touches 30 | const TouchEvents = function (element) { 31 | // hold onto all handlers to be able to turn them off 32 | const handlers = []; 33 | Object.keys(eventNameCategories).forEach((key) => { 34 | eventNameCategories[key].forEach((handler) => { 35 | handlers[handler] = []; 36 | }); 37 | }); 38 | 39 | const removeHandler = category => eventNameCategories[category] 40 | .forEach(handlerName => handlers[handlerName] 41 | .forEach(func => element.removeEventListener(handlerName, func))); 42 | 43 | // assign handlers for onMove, onPress, onRelease, onLeave 44 | Object.keys(eventNameCategories).forEach((category) => { 45 | Object.defineProperty(element, `on${capitalized(category)}`, { 46 | set: (handler) => { 47 | if (!element.addEventListener) { return; } 48 | if (handler == null) { 49 | removeHandler(category); 50 | return; 51 | } 52 | eventNameCategories[category].forEach((handlerName) => { 53 | const handlerFunc = (e) => { 54 | const pointer = (e.touches != null ? e.touches[0] : e); 55 | // for onRelease, pointer will be undefined 56 | if (pointer !== undefined) { 57 | const { clientX, clientY } = pointer; 58 | const [x, y] = convertToViewBox(element, clientX, clientY); 59 | defineGetter(e, "x", x); 60 | defineGetter(e, "y", y); 61 | } 62 | handler(e); 63 | }; 64 | handlers[handlerName].push(handlerFunc); 65 | element.addEventListener(handlerName, handlerFunc); 66 | }); 67 | }, 68 | enumerable: true, 69 | }); 70 | }); 71 | 72 | Object.defineProperty(element, "off", { value: () => off(element, handlers) }); 73 | }; 74 | 75 | export default TouchEvents; 76 | -------------------------------------------------------------------------------- /src/constructor/extensions/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import svgNS from "../../spec/namespace.js"; 5 | import window from "../../environment/window.js"; 6 | import makeCoordinates from "../../arguments/makeCoordinates.js"; 7 | import { str_string } from "../../environment/strings.js"; 8 | import TransformMethods from "./shared/transforms.js"; 9 | import URLMethods from "./shared/urls.js"; 10 | import { appendTo, setAttributes } from "./shared/dom.js"; 11 | /** 12 | * @description SVG text element 13 | * @memberof SVG 14 | * @linkcode SVG ./src/nodes/spec/text.js 11 15 | */ 16 | export default { 17 | text: { 18 | // assuming people will at most supply coordinate (x,y,z) and text 19 | args: (a, b, c) => makeCoordinates(...[a, b, c].flat()).slice(0, 2), 20 | init: (a, b, c, d) => { 21 | const element = window().document.createElementNS(svgNS, "text"); 22 | const text = [a, b, c, d].filter(el => typeof el === str_string).shift(); 23 | element.appendChild(window().document.createTextNode(text || "")); 24 | return element; 25 | }, 26 | methods: { 27 | ...TransformMethods, 28 | ...URLMethods, 29 | appendTo, 30 | setAttributes, 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/constructor/extensions/wedge/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeArcPath from "../shared/makeArcPath.js"; 5 | import { str_path } from "../../../environment/strings.js"; 6 | import TransformMethods from "../shared/transforms.js"; 7 | 8 | const wedgeArguments = (a, b, c, d, e) => [makeArcPath(a, b, c, d, e, true)]; 9 | 10 | export default { 11 | wedge: { 12 | nodeName: str_path, 13 | args: wedgeArguments, 14 | attributes: ["d"], 15 | methods: { 16 | setArc: (el, ...args) => el.setAttribute("d", wedgeArguments(...args)), 17 | ...TransformMethods, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/constructor/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import window from "../environment/window.js"; 5 | import svgNS from "../spec/namespace.js"; 6 | import nodes_children from "../spec/nodes_children.js"; 7 | import nodes_attributes from "../spec/nodes_attributes.js"; 8 | import { toCamel } from "../general/string.js"; 9 | import extensions from "./extensions/index.js"; 10 | 11 | const passthroughArgs = (...args) => args; 12 | /** 13 | * @description This is the main constructor for the library which generates 14 | * SVGElements (DOM elements) using createElementNS in the svg namespace. 15 | * Additionally, this element will be bound with methods to operate on the 16 | * element itself, which do things like set an attribute value or 17 | * create a child of this object. 18 | * Using this constructor, this library has full support for all elements 19 | * in the SVG spec (I think so, double check me on this), additionally, 20 | * some custom elements, for example "arrow" which makes a few shapes under 21 | * a single group. So this library is highly extendable, you can write 22 | * your own "arrow" objects, see more inside this directory's subdirectories. 23 | * @param {string} name the name of the element, although, slightly abstracted 24 | * from the actual element name, like "line" for because it supports 25 | * custom elements, "arrow", which in turn will create a or etc.. 26 | * @param {object} parent the parent to append this new node as a child to. 27 | */ 28 | const Constructor = (name, parent, ...initArgs) => { 29 | // the node name (like "line" for ) which is usually the 30 | // same as "name", but can sometimes differ in the case of custom elements 31 | const nodeName = extensions[name] && extensions[name].nodeName 32 | ? extensions[name].nodeName 33 | : name; 34 | const { init, args, methods } = extensions[name] || {}; 35 | const attributes = nodes_attributes[nodeName] || []; 36 | const children = nodes_children[nodeName] || []; 37 | 38 | // create the element itself under the svg namespace. 39 | // or, if the extension specifies a custom initializer, run it instead 40 | const element = init 41 | ? init(...initArgs) 42 | : window().document.createElementNS(svgNS, nodeName); 43 | 44 | // if the parent exists, make this element a child 45 | if (parent) { parent.appendChild(element); } 46 | 47 | // some element initializers can set some attributes set right after 48 | // initialization, if the extension specifies how to assign them, 49 | // if so, they will map to the indices in the nodes nodes_attributes. 50 | const processArgs = args || passthroughArgs; 51 | processArgs(...initArgs).forEach((v, i) => { 52 | element.setAttribute(nodes_attributes[nodeName][i], v); 53 | }); 54 | 55 | // if the extension specifies methods these will be bound to the object 56 | if (methods) { 57 | Object.keys(methods) 58 | .forEach(methodName => Object.defineProperty(element, methodName, { 59 | value: function () { 60 | return methods[methodName](element, ...arguments); 61 | }, 62 | })); 63 | } 64 | 65 | // camelCase functional style attribute setters, like .stroke() .strokeWidth() 66 | attributes.forEach((attribute) => { 67 | const attrNameCamel = toCamel(attribute); 68 | if (element[attrNameCamel]) { return; } 69 | Object.defineProperty(element, attrNameCamel, { 70 | value: function () { 71 | element.setAttribute(attribute, ...arguments); 72 | return element; 73 | }, 74 | }); 75 | }); 76 | 77 | // allow this element to initialize another element, and this 78 | // child element will be automatically appended to this element 79 | children.forEach((childNode) => { 80 | if (element[childNode]) { return; } 81 | const value = function () { return Constructor(childNode, element, ...arguments); }; 82 | Object.defineProperty(element, childNode, { value }); 83 | }); 84 | 85 | return element; 86 | }; 87 | 88 | export default Constructor; 89 | -------------------------------------------------------------------------------- /src/environment/detect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | const isBrowser = typeof window === "object" 6 | && typeof window.document === "object"; 7 | 8 | const isNodeOrBun = typeof process === "object" 9 | && typeof process.versions === "object" 10 | && (process.versions.node != null || process.versions.bun != null); 11 | 12 | const isDeno = typeof window === "object" 13 | && "Deno" in window 14 | && typeof window.Deno === "object"; 15 | 16 | const isBackend = isNodeOrBun || isDeno; 17 | 18 | const isWebWorker = typeof self === "object" 19 | && self.constructor 20 | && self.constructor.name === "DedicatedWorkerGlobalScope"; 21 | 22 | export { 23 | isBrowser, 24 | isBackend, 25 | isWebWorker, 26 | }; 27 | -------------------------------------------------------------------------------- /src/environment/lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | const lib = {}; 5 | 6 | export default lib; 7 | -------------------------------------------------------------------------------- /src/environment/messages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | export default { 5 | window: "window not set; svg.window = @xmldom/xmldom", 6 | }; 7 | -------------------------------------------------------------------------------- /src/environment/strings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | // frequently-used strings 5 | export const str_class = "class"; 6 | export const str_function = "function"; 7 | export const str_undefined = "undefined"; 8 | export const str_boolean = "boolean"; 9 | export const str_number = "number"; 10 | export const str_string = "string"; 11 | export const str_object = "object"; 12 | 13 | export const str_svg = "svg"; 14 | export const str_path = "path"; 15 | 16 | export const str_id = "id"; 17 | export const str_style = "style"; 18 | export const str_viewBox = "viewBox"; 19 | export const str_transform = "transform"; 20 | export const str_points = "points"; 21 | export const str_stroke = "stroke"; 22 | export const str_fill = "fill"; 23 | export const str_none = "none"; 24 | 25 | export const str_arrow = "arrow"; 26 | export const str_head = "head"; 27 | export const str_tail = "tail"; 28 | -------------------------------------------------------------------------------- /src/environment/window.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | /** 6 | * maintain one "window" object for both browser and nodejs. 7 | * if a browser window object exists, it will set to this, 8 | * including inside a node/react website for example in 9 | * backend-specific nodejs you will need to assign this 10 | * window object yourself to a XML DOM library, (url below), 11 | * by running: ear.window = xmldom (the default export) 12 | * 13 | * - @xmldom/xmldom: https://www.npmjs.com/package/@xmldom/xmldom 14 | * 15 | * note: xmldom supports DOMParser, XMLSerializer, and document, 16 | * but not cancelAnimationFrame, requestAnimationFrame, fetch, 17 | * which are used by this library. These parts of the library 18 | * will not work in Node. 19 | */ 20 | import { isBrowser } from "./detect.js"; 21 | import Messages from "./messages.js"; 22 | 23 | // store a pointer to the window object here. 24 | const windowContainer = { window: undefined }; 25 | 26 | // if we are in the browser, by default use the browser's "window". 27 | if (isBrowser) { windowContainer.window = window; } 28 | 29 | /** 30 | * @description create the window.document object, if it does not exist. 31 | */ 32 | const buildDocument = (newWindow) => new newWindow.DOMParser() 33 | .parseFromString(".", "text/html"); 34 | 35 | /** 36 | * @description This method allows the app to run in both a browser 37 | * environment, as well as some back-end environment like node.js. 38 | * In the case of a browser, no need to call this. 39 | * In the case of a back end environment, include some library such 40 | * as the popular @XMLDom package and pass it in as the argument here. 41 | */ 42 | export const setWindow = (newWindow) => { 43 | // make sure window has a document. xmldom does not, and requires it be built. 44 | if (!newWindow.document) { newWindow.document = buildDocument(newWindow); } 45 | windowContainer.window = newWindow; 46 | return windowContainer.window; 47 | }; 48 | 49 | /** 50 | * @description get the "window" object, which should have 51 | * DOMParser, XMLSerializer, and document. 52 | */ 53 | const RabbitEarWindow = () => { 54 | if (windowContainer.window === undefined) { 55 | throw new Error(Messages.window); 56 | } 57 | return windowContainer.window; 58 | }; 59 | 60 | export default RabbitEarWindow; 61 | -------------------------------------------------------------------------------- /src/general/algebra.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | /** @param {[number, number]} a @param {[number, number]} b @returns {[number, number]} */ 6 | export const svg_add2 = (a, b) => [a[0] + b[0], a[1] + b[1]]; 7 | 8 | /** @param {[number, number]} a @param {[number, number]} b @returns {[number, number]} */ 9 | export const svg_sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]]; 10 | 11 | /** @param {[number, number]} a @param {number} s @returns {[number, number]} */ 12 | export const svg_scale2 = (a, s) => [a[0] * s, a[1] * s]; 13 | 14 | /** @param {[number, number]} a */ 15 | export const svg_magnitudeSq2 = (a) => (a[0] ** 2) + (a[1] ** 2); 16 | 17 | /** @param {[number, number]} a */ 18 | export const svg_magnitude2 = (a) => Math.sqrt(svg_magnitudeSq2(a)); 19 | 20 | /** @param {[number, number]} a @param {[number, number]} b */ 21 | export const svg_distanceSq2 = (a, b) => svg_magnitudeSq2(svg_sub2(a, b)); 22 | 23 | /** @param {[number, number]} a @param {[number, number]} b */ 24 | export const svg_distance2 = (a, b) => Math.sqrt(svg_distanceSq2(a, b)); 25 | 26 | /** @param {number} a @param {number} d @returns {[number, number]} */ 27 | export const svg_polar_to_cart = (a, d) => [Math.cos(a) * d, Math.sin(a) * d]; 28 | 29 | /** @param {number[]} m1 @param {number[]} m2 */ 30 | export const svg_multiplyMatrices2 = (m1, m2) => [ 31 | m1[0] * m2[0] + m1[2] * m2[1], 32 | m1[1] * m2[0] + m1[3] * m2[1], 33 | m1[0] * m2[2] + m1[2] * m2[3], 34 | m1[1] * m2[2] + m1[3] * m2[3], 35 | m1[0] * m2[4] + m1[2] * m2[5] + m1[4], 36 | m1[1] * m2[4] + m1[3] * m2[5] + m1[5], 37 | ]; 38 | -------------------------------------------------------------------------------- /src/general/cdata.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import window from "../environment/window.js"; 5 | 6 | /** 7 | * @description Create a CDATASection containing text from the method 8 | * parameter. The CDATA is useful to wrap text which may contain 9 | * invalid characters or characters in need of escaping. 10 | * @param {string} text the text content to be placed inside the CData 11 | * @returns {CDATASection} a CDATA containing the given text. 12 | */ 13 | export const makeCDATASection = (text) => (new (window()).DOMParser()) 14 | .parseFromString("", "text/xml") 15 | .createCDATASection(text); 16 | -------------------------------------------------------------------------------- /src/general/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import * as svgMath from "./algebra.js"; 5 | import * as dom from "./dom.js"; 6 | import * as cdata from "./cdata.js"; 7 | import * as pathMethods from "./path.js"; 8 | import * as transforms from "./transforms.js"; 9 | import * as viewBox from "./viewBox.js"; 10 | 11 | export default { 12 | ...svgMath, 13 | ...dom, 14 | ...cdata, 15 | ...pathMethods, 16 | ...transforms, 17 | ...viewBox, 18 | }; 19 | -------------------------------------------------------------------------------- /src/general/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | const markerRegEx = /[MmLlSsQqLlHhVvCcSsQqTtAaZz]/g; 6 | const digitRegEx = /-?[0-9]*\.?\d+/g; 7 | 8 | /** 9 | * @description Path names in English 10 | */ 11 | export const pathCommandNames = { 12 | m: "move", 13 | l: "line", 14 | v: "vertical", 15 | h: "horizontal", 16 | a: "ellipse", 17 | c: "curve", 18 | s: "smoothCurve", 19 | q: "quadCurve", 20 | t: "smoothQuadCurve", 21 | z: "close", 22 | }; 23 | 24 | // make capitalized copies of each command 25 | Object.keys(pathCommandNames).forEach((key) => { 26 | const s = pathCommandNames[key]; 27 | pathCommandNames[key.toUpperCase()] = s.charAt(0).toUpperCase() + s.slice(1); 28 | }); 29 | 30 | /** 31 | * if the command is relative, it will build upon offset coordinate 32 | */ 33 | const add2path = (a, b) => [a[0] + (b[0] || 0), a[1] + (b[1] || 0)]; 34 | const getEndpoint = (command, values, offset = [0, 0]) => { 35 | const upper = command.toUpperCase(); 36 | let origin = command === upper ? [0, 0] : offset; 37 | // H and V (uppercase) absolutely position themselves along their 38 | // horiz or vert axis, but they should carry over the other component 39 | if (command === "V") { origin = [offset[0], 0]; } 40 | if (command === "H") { origin = [0, offset[1]]; } 41 | switch (upper) { 42 | case "V": return add2path(origin, [0, values[0]]); 43 | case "H": return add2path(origin, [values[0], 0]); 44 | case "M": 45 | case "L": 46 | case "T": return add2path(origin, values); 47 | case "A": return add2path(origin, [values[5], values[6]]); 48 | case "C": return add2path(origin, [values[4], values[5]]); 49 | case "S": 50 | case "Q": return add2path(origin, [values[2], values[3]]); 51 | case "Z": return undefined; // cannot be set locally. 52 | default: return origin; 53 | } 54 | }; 55 | 56 | /** 57 | * @description Parse a path "d" attribute into an array of objects, 58 | * where each object contains a command, and the numerical values. 59 | * @param {string} d the "d" attribute of a path. 60 | */ 61 | export const parsePathCommands = (d) => { 62 | const results = []; 63 | let match = markerRegEx.exec(d); 64 | while (match !== null) { 65 | results.push(match); 66 | match = markerRegEx.exec(d); 67 | } 68 | return results 69 | .map((result, i, arr) => ({ 70 | command: result[0], 71 | start: result.index, 72 | end: i === arr.length - 1 73 | ? d.length - 1 74 | : arr[(i + 1) % arr.length].index - 1, 75 | })) 76 | .map(({ command, start, end }) => { 77 | const valueString = d.substring(start + 1, end + 1); 78 | const strings = valueString.match(digitRegEx); 79 | const values = strings ? strings.map(parseFloat) : []; 80 | return { command, values }; 81 | }); 82 | }; 83 | 84 | export const parsePathCommandsWithEndpoints = (d) => { 85 | let pen = [0, 0]; 86 | const parsedCommands = parsePathCommands(d); 87 | const commands = parsedCommands 88 | .map(obj => ({ ...obj, end: undefined, start: undefined })); 89 | if (!commands.length) { return commands; } 90 | commands.forEach((command, i) => { 91 | commands[i].end = getEndpoint(command.command, command.values, pen); 92 | commands[i].start = i === 0 ? pen : commands[i - 1].end; 93 | pen = commands[i].end; 94 | }); 95 | const last = commands[commands.length - 1]; 96 | const firstDrawCommand = commands 97 | .filter(el => el.command.toUpperCase() !== "M" 98 | && el.command.toUpperCase() !== "Z") 99 | .shift(); 100 | if (last.command.toUpperCase() === "Z") { 101 | last.end = [...firstDrawCommand.start]; 102 | } 103 | return commands; 104 | }; 105 | -------------------------------------------------------------------------------- /src/general/string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | 5 | /** 6 | * @description make a unique identifier string 7 | * @returns {string} a unique identifier 8 | */ 9 | export const makeUUID = () => Math.random() 10 | .toString(36) 11 | .replace(/[^a-z]+/g, "") 12 | .concat("aaaaa") 13 | .substring(0, 5); 14 | 15 | /** 16 | * @description Convert a string into camel case from kebab or snake case. 17 | * @param {string} s a string in kebab or snake case 18 | * @returns {string} a string in camel case 19 | */ 20 | export const toCamel = (s) => s 21 | .replace(/([-_][a-z])/ig, $1 => $1 22 | .toUpperCase() 23 | .replace("-", "") 24 | .replace("_", "")); 25 | 26 | /** 27 | * @description Convert a string into kebab case from camel case. 28 | * Snake case does not work in this method. 29 | * @param {string} s a string in camel case 30 | * @returns {string} a string in kebab case 31 | */ 32 | export const toKebab = (s) => s 33 | .replace(/([a-z0-9])([A-Z])/g, "$1-$2") 34 | .replace(/([A-Z])([A-Z])(?=[a-z])/g, "$1-$2") 35 | .toLowerCase(); 36 | 37 | /** 38 | * @description Capitalize the first letter of a string. 39 | * @param {string} s a string 40 | * @returns {string} a copy of the string, capitalized. 41 | */ 42 | export const capitalized = (s) => s 43 | .charAt(0).toUpperCase() + s.slice(1); 44 | -------------------------------------------------------------------------------- /src/general/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { svg_multiplyMatrices2 } from "./algebra.js"; 5 | 6 | /** SVG transforms are in DEGREES ! */ 7 | 8 | /** 9 | * @description parse the value of a SVG transform attribute 10 | * @param {string} transform a CSS/SVG transform, for example 11 | * "translate(20 30) rotate(30) skewY(10)" 12 | * @returns {{ transform: string, parameters: number[] }[]} array of objects 13 | * representing method calls where the "transform" is the transform name and 14 | * the "parameters" is a list of floats. 15 | */ 16 | export const parseTransform = (transform) => { 17 | const parsed = transform.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?\s*)+\))+/g); 18 | if (!parsed) { return []; } 19 | const listForm = parsed.map(a => a.match(/[\w\.\-]+/g)); 20 | return listForm.map(a => ({ 21 | transform: a.shift(), 22 | parameters: a.map(p => parseFloat(p)), 23 | })); 24 | }; 25 | 26 | /** 27 | * @description convert the arguments of each SVG affine transform type 28 | * into matrix form. 29 | */ 30 | const matrixFormTranslate = function (params) { 31 | switch (params.length) { 32 | case 1: return [1, 0, 0, 1, params[0], 0]; 33 | case 2: return [1, 0, 0, 1, params[0], params[1]]; 34 | default: console.warn(`improper translate, ${params}`); 35 | } 36 | return undefined; 37 | }; 38 | 39 | const matrixFormRotate = function (params) { 40 | const cos_p = Math.cos(params[0] / (180 * Math.PI)); 41 | const sin_p = Math.sin(params[0] / (180 * Math.PI)); 42 | switch (params.length) { 43 | case 1: return [cos_p, sin_p, -sin_p, cos_p, 0, 0]; 44 | case 3: return [cos_p, sin_p, -sin_p, cos_p, 45 | -params[1] * cos_p + params[2] * sin_p + params[1], 46 | -params[1] * sin_p - params[2] * cos_p + params[2]]; 47 | default: console.warn(`improper rotate, ${params}`); 48 | } 49 | return undefined; 50 | }; 51 | 52 | const matrixFormScale = function (params) { 53 | switch (params.length) { 54 | case 1: return [params[0], 0, 0, params[0], 0, 0]; 55 | case 2: return [params[0], 0, 0, params[1], 0, 0]; 56 | default: console.warn(`improper scale, ${params}`); 57 | } 58 | return undefined; 59 | }; 60 | 61 | const matrixFormSkewX = function (params) { 62 | return [1, 0, Math.tan(params[0] / (180 * Math.PI)), 1, 0, 0]; 63 | }; 64 | 65 | const matrixFormSkewY = function (params) { 66 | return [1, Math.tan(params[0] / (180 * Math.PI)), 0, 1, 0, 0]; 67 | }; 68 | 69 | const matrixForm = function (transformType, params) { 70 | switch (transformType) { 71 | case "translate": return matrixFormTranslate(params); 72 | case "rotate": return matrixFormRotate(params); 73 | case "scale": return matrixFormScale(params); 74 | case "skewX": return matrixFormSkewX(params); 75 | case "skewY": return matrixFormSkewY(params); 76 | case "matrix": return params; 77 | default: console.warn(`unknown transform type ${transformType}`); 78 | } 79 | return undefined; 80 | }; 81 | 82 | export const transformStringToMatrix = function (string) { 83 | return parseTransform(string) 84 | .map(el => matrixForm(el.transform, el.parameters)) 85 | .filter(a => a !== undefined) 86 | .reduce((a, b) => svg_multiplyMatrices2(a, b), [1, 0, 0, 1, 0, 0]); 87 | }; 88 | -------------------------------------------------------------------------------- /src/general/viewBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import makeViewBox from "../arguments/makeViewBox.js"; 5 | import { 6 | str_string, 7 | str_viewBox, 8 | } from "../environment/strings.js"; 9 | 10 | export const setViewBox = (element, ...args) => { 11 | // are they giving us pre-formatted string, or a list of numbers 12 | const viewBox = args.length === 1 && typeof args[0] === str_string 13 | ? args[0] 14 | : makeViewBox(...args); 15 | if (viewBox) { 16 | element.setAttribute(str_viewBox, viewBox); 17 | } 18 | return element; 19 | }; 20 | 21 | export const getViewBox = function (element) { 22 | const vb = element.getAttribute(str_viewBox); 23 | return (vb == null 24 | ? undefined 25 | : vb.split(" ").map(n => parseFloat(n))); 26 | }; 27 | 28 | export const convertToViewBox = function (svg, x, y) { 29 | const pt = svg.createSVGPoint(); 30 | pt.x = x; 31 | pt.y = y; 32 | // todo: i thought this threw an error once. something about getScreenCTM. 33 | const svgPoint = pt.matrixTransform(svg.getScreenCTM().inverse()); 34 | return [svgPoint.x, svgPoint.y]; 35 | }; 36 | 37 | export const foldToViewBox = ({ vertices_coords }) => { 38 | if (!vertices_coords) { return undefined; } 39 | const min = [Infinity, Infinity]; 40 | const max = [-Infinity, -Infinity]; 41 | vertices_coords.forEach(coord => [0, 1].forEach(i => { 42 | min[i] = Math.min(coord[i], min[i]); 43 | max[i] = Math.max(coord[i], max[i]); 44 | })); 45 | return [min[0], min[1], max[0] - min[0], max[1] - min[1]].join(" "); 46 | }; 47 | 48 | /* 49 | export const translateViewBox = function (svg, dx, dy) { 50 | const viewBox = getViewBox(svg); 51 | if (viewBox == null) { 52 | setDefaultViewBox(svg); 53 | } 54 | viewBox[0] += dx; 55 | viewBox[1] += dy; 56 | svg.setAttributeNS(null, vB, viewBox.join(" ")); 57 | }; 58 | 59 | export const scaleViewBox = function (svg, scale, origin_x = 0, origin_y = 0) { 60 | if (Math.abs(scale) < 1e-8) { scale = 0.01; } 61 | const matrix = svg.createSVGMatrix() 62 | .translate(origin_x, origin_y) 63 | .scale(1 / scale) 64 | .translate(-origin_x, -origin_y); 65 | const viewBox = getViewBox(svg); 66 | if (viewBox == null) { 67 | setDefaultViewBox(svg); 68 | } 69 | const top_left = svg.createSVGPoint(); 70 | const bot_right = svg.createSVGPoint(); 71 | [top_left.x, top_left.y] = viewBox; 72 | bot_right.x = viewBox[0] + viewBox[2]; 73 | bot_right.y = viewBox[1] + viewBox[3]; 74 | const new_top_left = top_left.matrixTransform(matrix); 75 | const new_bot_right = bot_right.matrixTransform(matrix); 76 | setViewBox(svg, 77 | new_top_left.x, 78 | new_top_left.y, 79 | new_bot_right.x - new_top_left.x, 80 | new_bot_right.y - new_top_left.y); 81 | }; 82 | 83 | */ 84 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import { setWindow } from "./environment/window.js"; 5 | import NS from "./spec/namespace.js"; 6 | import nodes_attributes from "./spec/nodes_attributes.js"; 7 | import nodes_children from "./spec/nodes_children.js"; 8 | import colors from "./colors/index.js"; 9 | import general from "./general/index.js"; 10 | import extensions from "./constructor/extensions/index.js"; 11 | import { svg, constructors } from "./constructor/elements.js"; 12 | 13 | const library = { 14 | NS, 15 | nodes_attributes, 16 | nodes_children, 17 | extensions, 18 | ...colors, 19 | ...general, 20 | ...constructors, 21 | }; 22 | 23 | // the top level container object is also an constructor 24 | const SVG = Object.assign(svg, library); 25 | 26 | // the window object, from which the document is used to createElement. 27 | // if using a browser, no need to interact with this, 28 | // if using node.js, set this to the library @xmldom/xmldom. 29 | Object.defineProperty(SVG, "window", { 30 | enumerable: false, 31 | set: setWindow, 32 | }); 33 | 34 | /** 35 | * @type {Function} 36 | */ 37 | export default SVG; 38 | -------------------------------------------------------------------------------- /src/spec/classes_nodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | const classes_nodes = { 5 | svg: [ 6 | "svg", 7 | ], 8 | defs: [ 9 | "defs", // can only be inside svg 10 | ], 11 | // anywhere, usually top level SVG, or 12 | header: [ 13 | "desc", 14 | "filter", 15 | "metadata", 16 | "style", 17 | "script", 18 | "title", 19 | "view", 20 | ], 21 | cdata: [ 22 | "cdata", 23 | ], 24 | group: [ 25 | "g", 26 | ], 27 | visible: [ 28 | "circle", 29 | "ellipse", 30 | "line", 31 | "path", 32 | "polygon", 33 | "polyline", 34 | "rect", 35 | 36 | // extensions to the SVG spec 37 | "arc", 38 | "arrow", 39 | "curve", 40 | "parabola", 41 | "roundRect", 42 | "wedge", 43 | "origami", 44 | ], 45 | text: [ 46 | "text", 47 | ], 48 | // can contain drawings 49 | // anywhere, usually top level SVG, or 50 | invisible: [ 51 | "marker", 52 | "symbol", 53 | "clipPath", 54 | "mask", 55 | ], 56 | // inside 57 | patterns: [ 58 | "linearGradient", 59 | "radialGradient", 60 | "pattern", 61 | ], 62 | childrenOfText: [ 63 | "textPath", 64 | "tspan", 65 | ], 66 | gradients: [ // children of gradients ( ) 67 | "stop", 68 | ], 69 | filter: [ // children of filter 70 | "feBlend", 71 | "feColorMatrix", 72 | "feComponentTransfer", 73 | "feComposite", 74 | "feConvolveMatrix", 75 | "feDiffuseLighting", 76 | "feDisplacementMap", 77 | "feDistantLight", 78 | "feDropShadow", 79 | "feFlood", 80 | "feFuncA", 81 | "feFuncB", 82 | "feFuncG", 83 | "feFuncR", 84 | "feGaussianBlur", 85 | "feImage", 86 | "feMerge", 87 | "feMergeNode", 88 | "feMorphology", 89 | "feOffset", 90 | "fePointLight", 91 | "feSpecularLighting", 92 | "feSpotLight", 93 | "feTile", 94 | "feTurbulence", 95 | ], 96 | }; 97 | 98 | export default classes_nodes; 99 | -------------------------------------------------------------------------------- /src/spec/namespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | /** 5 | * @description The XML namespace for SVG. The value of the SVG attribute xmlns. 6 | * @constant {string} 7 | * @default 8 | */ 9 | export default "http://www.w3.org/2000/svg"; 10 | -------------------------------------------------------------------------------- /src/spec/nodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import classes_nodes from "./classes_nodes.js"; 5 | 6 | export const nodeNames = Object.values(classes_nodes).flat(); 7 | 8 | export default classes_nodes; 9 | -------------------------------------------------------------------------------- /src/spec/nodes_attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import classes_attributes from "./classes_attributes.js"; 5 | import classes_nodes from "./classes_nodes.js"; 6 | import { 7 | str_svg, 8 | str_viewBox, 9 | str_points, 10 | str_id, 11 | } from "../environment/strings.js"; 12 | /** 13 | * @description This is the base object containing mostly the unique 14 | * attributes associated with an individual SVG element type. 15 | * After initialization, this object will be modified to contain all 16 | * nodes in the spec, and each node with a list of all possible attributes. 17 | * @constant {object} 18 | * @note the order of these indices matter. the rest which will be added, 19 | * not important. 20 | */ 21 | const nodes_attributes = { 22 | svg: [str_viewBox], 23 | line: ["x1", "y1", "x2", "y2"], 24 | rect: ["x", "y", "width", "height"], 25 | circle: ["cx", "cy", "r"], 26 | ellipse: ["cx", "cy", "rx", "ry"], 27 | polygon: [str_points], 28 | polyline: [str_points], 29 | path: ["d"], 30 | text: ["x", "y"], 31 | mask: [str_id], 32 | symbol: [str_id], 33 | clipPath: [str_id, "clip-rule"], 34 | marker: [ 35 | str_id, 36 | "markerHeight", 37 | "markerUnits", 38 | "markerWidth", 39 | "orient", 40 | "refX", 41 | "refY", 42 | ], 43 | linearGradient: ["x1", "x2", "y1", "y2"], 44 | radialGradient: ["cx", "cy", "r", "fr", "fx", "fy"], 45 | stop: ["offset", "stop-color", "stop-opacity"], 46 | pattern: ["patternContentUnits", "patternTransform", "patternUnits"], 47 | }; 48 | 49 | // Fill out the keys of nodes_attributes, make sure it includes one 50 | // key for every SVG element type in the SVG spec. 51 | // nodes 52 | // .filter(nodeName => !nodes_attributes[nodeName]) 53 | // .forEach(nodeName => { nodes_attributes[nodeName] = []; }); 54 | 55 | // Fill out the values in nodes_attributes, these are values like 56 | // "stroke" or "fill" which end up getting repeatedly applied to many 57 | // elements. This way the library can be shipped in a smaller state and 58 | // build upon initialization. 59 | const additionalNodeAttributes = [{ 60 | nodes: [str_svg, "defs", "g"].concat(classes_nodes.visible, classes_nodes.text), 61 | attr: classes_attributes.presentation, 62 | }, { 63 | nodes: ["filter"], 64 | attr: classes_attributes.effects, 65 | }, { 66 | // todo: should we include "svg" here? 67 | nodes: classes_nodes.childrenOfText.concat("text"), 68 | attr: classes_attributes.text, 69 | }, { 70 | nodes: classes_nodes.filter, 71 | attr: classes_attributes.effects, 72 | }, { 73 | nodes: classes_nodes.gradients, 74 | attr: classes_attributes.gradient, 75 | }]; 76 | 77 | // for every node in the set, add all attributes associated with the node. 78 | additionalNodeAttributes 79 | .forEach(el => el.nodes 80 | .forEach(nodeName => { 81 | if (!nodes_attributes[nodeName]) { nodes_attributes[nodeName] = []; } 82 | nodes_attributes[nodeName].push(...el.attr); 83 | })); 84 | 85 | export default nodes_attributes; 86 | -------------------------------------------------------------------------------- /src/spec/nodes_children.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | import classes_nodes from "./classes_nodes.js"; 5 | 6 | // const customPrimitives = []; 7 | 8 | const headerStuff = [ 9 | classes_nodes.header, 10 | classes_nodes.invisible, 11 | classes_nodes.patterns, 12 | ].flat(); 13 | 14 | const drawingShapes = [ 15 | classes_nodes.group, 16 | classes_nodes.visible, 17 | classes_nodes.text, 18 | // customPrimitives, 19 | ].flat(); 20 | 21 | const nodes_children = { 22 | // svg 23 | svg: [["svg", "defs"], headerStuff, drawingShapes].flat(), 24 | 25 | // defs 26 | defs: headerStuff, 27 | 28 | // header 29 | // desc: [], 30 | filter: classes_nodes.filter, 31 | // metadata: [], 32 | // style: [], 33 | // script: [], 34 | // title: [], 35 | // view: [], 36 | 37 | // cdata 38 | // cdata: [], 39 | 40 | // group 41 | g: drawingShapes, 42 | 43 | // visible drawing primitives 44 | // circle: [], 45 | // ellipse: [], 46 | // line: [], 47 | // path: [], 48 | // polygon: [], 49 | // polyline: [], 50 | // rect: [], 51 | 52 | // text 53 | text: classes_nodes.childrenOfText, 54 | 55 | // invisible. can contain drawing primitives 56 | marker: drawingShapes, 57 | symbol: drawingShapes, 58 | clipPath: drawingShapes, 59 | mask: drawingShapes, 60 | 61 | // patterns and gradients 62 | linearGradient: classes_nodes.gradients, 63 | radialGradient: classes_nodes.gradients, 64 | // pattern: [], 65 | 66 | // can be a child of text 67 | // textPath: [], 68 | // tspan: [], 69 | 70 | // can be a child of gradients 71 | // stop: [], 72 | 73 | // can be a child of filter 74 | // feBlend: [], 75 | // feColorMatrix: [], 76 | // feComponentTransfer: [], 77 | // feComposite: [], 78 | // feConvolveMatrix: [], 79 | // feDiffuseLighting: [], 80 | // feDisplacementMap: [], 81 | // feDistantLight: [], 82 | // feDropShadow: [], 83 | // feFlood: [], 84 | // feFuncA: [], 85 | // feFuncB: [], 86 | // feFuncG: [], 87 | // feFuncR: [], 88 | // feGaussianBlur: [], 89 | // feImage: [], 90 | // feMerge: [], 91 | // feMergeNode: [], 92 | // feMorphology: [], 93 | // feOffset: [], 94 | // fePointLight: [], 95 | // feSpecularLighting: [], 96 | // feSpotLight: [], 97 | // feTile: [], 98 | // feTurbulence: [], 99 | }; 100 | 101 | export default nodes_children; 102 | -------------------------------------------------------------------------------- /tests/arrow.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import SVG from "../src/index.js"; 3 | 4 | test("arrow", () => { 5 | // svg.size(2.5, 1) 6 | // .strokeWidth(0.01) 7 | // .padding(0.25); 8 | 9 | // const options = { 10 | // bend: 0.3, 11 | // head: { 12 | // width: 0.1, 13 | // height: 0.13, 14 | // }, 15 | // fill: "red", 16 | // }; 17 | 18 | // const layer = svg.g(); 19 | 20 | // svg.controls(2) 21 | // .svg(() => svg.circle(0.04).fill("blue")) 22 | // .position(() => [Math.random(), Math.random()]) 23 | // .onChange((p, i, pts) => { 24 | // layer.removeChildren(); 25 | // const arrow = layer.arrow(options) 26 | // .points(...pts) 27 | // arrow.getLine() 28 | // .strokeWidth(0.03) 29 | // .stroke("black"); 30 | 31 | // }, true); 32 | expect(true).toBe(true); 33 | }); 34 | 35 | test("arrow object", () => { 36 | expect(true).toBe(true); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/circle.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("circle arguments", () => { 8 | expect(SVG.circle(1).getAttribute("r")).toBe("1"); 9 | expect(SVG.circle(5).getAttribute("r")).toBe("5"); 10 | expect(SVG.circle(2, 3).getAttribute("cx")).toBe("2"); 11 | expect(SVG.circle(2, 3).getAttribute("cy")).toBe("3"); 12 | expect(SVG.circle(2, 3).getAttribute("r") === null 13 | || SVG.circle(2, 3).getAttribute("r") === "").toBe(true); 14 | 15 | expect(SVG.circle(1, 2, 3).getAttribute("cx")).toBe("1"); 16 | expect(SVG.circle(1, 2, 3).getAttribute("cy")).toBe("2"); 17 | expect(SVG.circle(1, 2, 3).getAttribute("r")).toBe("3"); 18 | 19 | expect(parseFloat(SVG.circle(1, 2, 3, 4).getAttribute("r"))) 20 | .toBeCloseTo(2 * Math.sqrt(2)); 21 | expect(SVG.circle(1, 2, 3, 4).getAttribute("cx")).toBe("1"); 22 | expect(SVG.circle(1, 2, 3, 4).getAttribute("cy")).toBe("2"); 23 | }); 24 | 25 | test("circle setters", () => { 26 | expect(SVG.circle().radius(5).getAttribute("r")).toBe("5"); 27 | expect(SVG.circle().setRadius(5).getAttribute("r")).toBe("5"); 28 | expect(SVG.circle().origin(2, 3).getAttribute("cx")).toBe("2"); 29 | expect(SVG.circle().origin(2, 3).getAttribute("cy")).toBe("3"); 30 | expect(SVG.circle().setOrigin(2, 3).getAttribute("cx")).toBe("2"); 31 | expect(SVG.circle().setOrigin(2, 3).getAttribute("cy")).toBe("3"); 32 | expect(SVG.circle().center(2, 3).getAttribute("cx")).toBe("2"); 33 | expect(SVG.circle().center(2, 3).getAttribute("cy")).toBe("3"); 34 | expect(SVG.circle().setCenter(2, 3).getAttribute("cx")).toBe("2"); 35 | expect(SVG.circle().setCenter(2, 3).getAttribute("cy")).toBe("3"); 36 | expect(SVG.circle().position(2, 3).getAttribute("cx")).toBe("2"); 37 | expect(SVG.circle().position(2, 3).getAttribute("cy")).toBe("3"); 38 | expect(SVG.circle().setPosition(2, 3).getAttribute("cx")).toBe("2"); 39 | expect(SVG.circle().setPosition(2, 3).getAttribute("cy")).toBe("3"); 40 | 41 | const attrs = ["cx", "cy"]; 42 | let c = SVG.circle(); 43 | c.setCenter(1, 2, 3, 4); 44 | attrs.forEach((attr, i) => expect(c.getAttribute(attr)).toBe(String([1, 2][i]))); 45 | expect(c.attributes.length).toBe(2); 46 | 47 | c.setRadius(10); 48 | attrs.forEach((attr, i) => expect(c.getAttribute(attr)).toBe(String([1, 2][i]))); 49 | expect(c.getAttribute("r")).toBe("10"); 50 | expect(c.attributes.length).toBe(3); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/class.id.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("class", () => { 8 | const svg = SVG(); 9 | if (typeof svg.classList === "undefined") { 10 | expect(true).toBe(true); 11 | return; 12 | } 13 | svg.classList.add("big"); 14 | expect(svg.getAttribute("class")).toBe("big"); 15 | svg.classList.add("medium"); 16 | expect(svg.getAttribute("class")).toBe("big medium"); 17 | svg.classList.remove("big"); 18 | expect(svg.getAttribute("class")).toBe("medium"); 19 | svg.className = "small"; 20 | expect(svg.getAttribute("class")).toBe("small"); 21 | 22 | const line = SVG.line(); 23 | line.id = "five"; 24 | expect(line.getAttribute("id")).toBe("five"); 25 | 26 | const circle = SVG.circle(); 27 | circle.classList.remove("apple"); 28 | expect(circle.getAttribute("class")).toBe(""); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/clean.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | // const fs = require("fs"); 3 | 4 | // fs.existsSync(path) 5 | fs.readdirSync("./") 6 | .filter(s => s.match(/svg(.*).js/)) 7 | // .forEach(path => console.log(path)); 8 | .forEach(path => fs.unlinkSync(`./${path}`)); 9 | -------------------------------------------------------------------------------- /tests/coordinates.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("", () => expect(true).toBe(true)); 8 | 9 | // test("coordinates, two points", () => { 10 | // SVG.makeCoordinates(1, 2, 3, 4) 11 | // .forEach((n, i) => expect(n).toBe([1, 2, 3, 4][i])); 12 | // SVG.makeCoordinates([1, 2], [3, 4]) 13 | // .forEach((n, i) => expect(n).toBe([1, 2, 3, 4][i])); 14 | // SVG.makeCoordinates([1, 2, 3], [4, 5, 6]) 15 | // .forEach((n, i) => expect(n).toBe([1, 2, 4, 5][i])); 16 | // SVG.makeCoordinates([1], [2]) 17 | // .forEach((n, i) => expect(n).toBe([1, undefined, 2, undefined][i])); 18 | // }); 19 | 20 | // test("coordinates, not two points", () => { 21 | // expect(SVG.makeCoordinates().length).toBe(0); 22 | // expect(SVG.makeCoordinates([]).length).toBe(0); 23 | // expect(SVG.makeCoordinates([[]]).length).toBe(0); 24 | // expect(SVG.makeCoordinates([], []).length).toBe(0); 25 | 26 | // SVG.makeCoordinates([1, 2], [3, 4], [5, 6]) 27 | // .forEach((n, i) => expect(n).toBe([1, 2, 3, 4, 5, 6][i])); 28 | // }); 29 | -------------------------------------------------------------------------------- /tests/dom.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("removeChildren()", () => { 8 | const svg = SVG(); 9 | svg.line(1, 2, 3, 4); 10 | expect(svg.childNodes.length).toBe(1); 11 | svg.removeChildren(); 12 | expect(svg.childNodes.length).toBe(0); 13 | }); 14 | 15 | // test("appendTo()", () => { 16 | // const svg = SVG(); 17 | // expect(svg.childNodes.length).toBe(0); 18 | // const line = SVG.line(); 19 | // const circle = SVG.circle(); 20 | // const ellipse = SVG.ellipse(); 21 | // const rect = SVG.rect(); 22 | // const path = SVG.path(); 23 | // const polygon = SVG.polygon(); 24 | // const polyline = SVG.polyline(); 25 | // const group = SVG.g(); 26 | // [line, circle, ellipse, rect, path, polygon, polyline, group] 27 | // .forEach(primitive => primitive.appendTo(svg)); 28 | // expect(svg.childNodes.length).toBe(8); 29 | // }); 30 | 31 | // test("setAttributes()", () => { 32 | // const props = { a: "10", display: "block", style: "color:red" }; 33 | // const line = SVG.line(); 34 | // line.setAttributes(props); 35 | // expect(line.getAttribute("display")).toBe("block"); 36 | // const group = SVG.g(); 37 | // group.setAttributes(props); 38 | // expect(group.getAttribute("style")).toBe("color:red"); 39 | // const svg = SVG(); 40 | // svg.setAttributes(props); 41 | // expect(svg.getAttribute("display")).toBe("block"); 42 | // }); 43 | -------------------------------------------------------------------------------- /tests/ear.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = global || self, global.ear = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | const obj = { svg: () => {} }; 8 | obj.prototype = Object.prototype; 9 | 10 | const rabbitEar = { 11 | origami: () => {}, 12 | graph: () => {}, 13 | cp: () => {}, 14 | vector: obj, 15 | line: obj, 16 | ray: obj, 17 | segment: obj, 18 | circle: obj, 19 | ellipse: obj, 20 | rect: obj, 21 | polygon: obj, 22 | matrix: obj, 23 | plane: obj, 24 | }; 25 | const use = function (library) { 26 | if (typeof library !== "function" 27 | || library === null 28 | || typeof library.core.attach !== "function") { 29 | return; 30 | } 31 | library.core.attach(this); 32 | }; 33 | rabbitEar.use = use.bind(rabbitEar); 34 | return rabbitEar; 35 | }))); 36 | -------------------------------------------------------------------------------- /tests/ellipse.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("ellipse setters", () => { 8 | expect(SVG.ellipse().radius(5, 6).getAttribute("rx")).toBe("5"); 9 | expect(SVG.ellipse().radius(5, 6).getAttribute("ry")).toBe("6"); 10 | expect(SVG.ellipse().setRadius(5, 6).getAttribute("rx")).toBe("5"); 11 | expect(SVG.ellipse().setRadius(5, 6).getAttribute("ry")).toBe("6"); 12 | expect(SVG.ellipse().origin(2, 3).getAttribute("cx")).toBe("2"); 13 | expect(SVG.ellipse().origin(2, 3).getAttribute("cy")).toBe("3"); 14 | expect(SVG.ellipse().setOrigin(2, 3).getAttribute("cx")).toBe("2"); 15 | expect(SVG.ellipse().setOrigin(2, 3).getAttribute("cy")).toBe("3"); 16 | expect(SVG.ellipse().center(2, 3).getAttribute("cx")).toBe("2"); 17 | expect(SVG.ellipse().center(2, 3).getAttribute("cy")).toBe("3"); 18 | expect(SVG.ellipse().setCenter(2, 3).getAttribute("cx")).toBe("2"); 19 | expect(SVG.ellipse().setCenter(2, 3).getAttribute("cy")).toBe("3"); 20 | expect(SVG.ellipse().position(2, 3).getAttribute("cx")).toBe("2"); 21 | expect(SVG.ellipse().position(2, 3).getAttribute("cy")).toBe("3"); 22 | expect(SVG.ellipse().setPosition(2, 3).getAttribute("cx")).toBe("2"); 23 | expect(SVG.ellipse().setPosition(2, 3).getAttribute("cy")).toBe("3"); 24 | // incomplete info 25 | expect(SVG.ellipse().setRadius(5).getAttribute("rx")).toBe("5"); 26 | // expect(SVG.ellipse().setRadius(5).getAttribute("ry")).toBe(""); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/environment.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | 3 | test("environment", () => { 4 | expect(true).toBe(true); 5 | // console.log("window", window); 6 | // console.log("window.document", window.document); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/line.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("argument parsing, line", () => { 8 | const lines = [ 9 | SVG.line(1, 2, 3, 4), 10 | SVG.line([1, 2, 3, 4]), 11 | SVG.line([[1, 2, 3, 4]]), 12 | SVG.line([1, 2], [3, 4]), 13 | SVG.line({ x: 1, y: 2 }, { x: 3, y: 4 }), 14 | SVG.line([{ x: 1, y: 2 }, { x: 3, y: 4 }]), 15 | SVG.line([1, 2, 9], [3, 4, 9]), 16 | SVG.line([[1, 2, 9], [3, 4, 9]]), 17 | SVG.line({ x: 1, y: 2, z: 9 }, { x: 3, y: 4, z: 9 }), 18 | ]; 19 | // SVG.line([1], [2], [3], [4]), 20 | // SVG.line([{x:1, y:2}], [{x:3, y:4}]), 21 | // SVG.line([[{x:1, y:2}], [{x:3, y:4}]]), 22 | const result = lines 23 | .map(el => ["x1", "y1", "x2", "y2"] 24 | .map(attr => el.getAttribute(attr)) 25 | .map((value, i) => value === String(i + 1)) 26 | .reduce((a, b) => a && b, true)) 27 | .reduce((a, b) => a && b, true); 28 | expect(result).toBe(true); 29 | }); 30 | 31 | test("line arguments, missing arguments", () => { 32 | const result1 = SVG.line(1, 2, 3); 33 | expect(result1.getAttribute("x2")).toBe("3"); 34 | expect(result1.getAttribute("y2")).toBe(""); 35 | 36 | const result2 = SVG.line({ x: 1, y: 2 }, { x: 3 }); 37 | expect(result2.getAttribute("x2")).toBe("3"); 38 | // expect(result2.getAttribute("y2")).toBe(""); 39 | }); 40 | 41 | test("line setters", () => { 42 | const attrs = ["x1", "y1", "x2", "y2"]; 43 | 44 | let l = SVG.line(); 45 | l.setPoints(1, 2, 3, 4); 46 | attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([1, 2, 3, 4][i]))); 47 | 48 | l.setPoints([[1, 2, 3, 4]]); 49 | attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([1, 2, 3, 4][i]))); 50 | 51 | l.setPoints([[1, 2], [3, 4]]); 52 | attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([1, 2, 3, 4][i]))); 53 | expect(l.attributes.length).toBe(4); 54 | 55 | // this will not work 56 | l.setPoints([[1, 2], [3, 4], 5, [6, 7]]); 57 | // attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([1, 2, 3, 4][i]))); 58 | // expect(l.attributes.length).toBe(4); 59 | 60 | // this will not work 61 | l.setPoints("9", "8", "7", "6"); 62 | // attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([1, 2, 3, 4][i]))); 63 | 64 | l.setPoints({ x: 5, y: 6 }, { x: 7, y: 8 }, { x: 9, y: 10 }); 65 | attrs.forEach((attr, i) => expect(l.getAttribute(attr)).toBe(String([5, 6, 7, 8][i]))); 66 | expect(l.attributes.length).toBe(4); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/lineno.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const repoName = "SVG"; 4 | 5 | const regex = /\*(\s*)\@linkcode.*/g; 6 | 7 | const processFile = (path) => { 8 | // console.log("checking file", path); 9 | const contents = fs.readFileSync(path, "utf8"); 10 | const matches = contents.match(regex); 11 | if (!matches) { return; } 12 | const lineNumbers = contents 13 | .split(/\r?\n/) 14 | .map((line, i) => (line.match(regex) !== null ? i : undefined)) 15 | .filter(a => a !== undefined) 16 | .map(num => num + 1); // line numbers start with 1 17 | if (matches.length !== lineNumbers.length) { 18 | console.log("unexpected error! the lengths of matches don't equal"); return; 19 | } 20 | const values = lineNumbers.map(num => `${repoName} ${path} ${num}`); 21 | const lines = values.map(value => `* @linkcode ${value}`); 22 | 23 | let count = 0; 24 | const fn = () => lines[count++]; 25 | const modified = contents.replace(regex, fn); 26 | fs.writeFileSync(path, modified); 27 | 28 | // console.log(`modified ${path} with ${matches.length} line number entries`); 29 | }; 30 | 31 | const searchDir = (path) => { 32 | const contents = fs.readdirSync(path, { withFileTypes: true }); 33 | const files = contents.filter(el => el.isFile()); 34 | const directories = contents.filter(el => el.isDirectory()); 35 | // console.log("checking dir", path); 36 | // console.log("files", files); 37 | // console.log("directories", directories); 38 | files.forEach(el => processFile(`${path}/${el.name}`)); 39 | directories.forEach(el => searchDir(`${path}/${el.name}`)); 40 | }; 41 | 42 | searchDir("./src"); 43 | -------------------------------------------------------------------------------- /tests/nodes.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | const NodeAndChildren = { 8 | svg: ["svg", "defs", "desc", "filter", "metadata", "style", "script", "title", "view", "linearGradient", "radialGradient", "pattern", "marker", "symbol", "clipPath", "mask", "g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 9 | g: ["g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 10 | // text: ["textPath", "tspan"], // text has another "text" child node that doesn't match 11 | linearGradient: ["stop"], 12 | radialGradient: ["stop"], 13 | defs: ["desc", "filter", "metadata", "style", "script", "title", "view", "linearGradient", "radialGradient", "pattern", "marker", "symbol", "clipPath", "mask"], 14 | filter: ["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"], 15 | marker: ["g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 16 | symbol: ["g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 17 | clipPath: ["g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 18 | mask: ["g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text"], 19 | }; 20 | 21 | const NodesNames = [ 22 | "svg", 23 | "line", 24 | "rect", 25 | "circle", 26 | "ellipse", 27 | "polygon", 28 | "polyline", 29 | "path", 30 | "mask", 31 | "symbol", 32 | "clipPath", 33 | "marker", 34 | "linearGradient", 35 | "radialGradient", 36 | "stop", 37 | "pattern", 38 | "defs", 39 | "desc", 40 | "filter", 41 | "metadata", 42 | "style", 43 | "script", 44 | "title", 45 | "view", 46 | "cdata", 47 | "g", 48 | "text", 49 | "textPath", 50 | "tspan", 51 | "feBlend", 52 | "feColorMatrix", 53 | "feComponentTransfer", 54 | "feComposite", 55 | "feConvolveMatrix", 56 | "feDiffuseLighting", 57 | "feDisplacementMap", 58 | "feDistantLight", 59 | "feDropShadow", 60 | "feFlood", 61 | "feFuncA", 62 | "feFuncB", 63 | "feFuncG", 64 | "feFuncR", 65 | "feGaussianBlur", 66 | "feImage", 67 | "feMerge", 68 | "feMergeNode", 69 | "feMorphology", 70 | "feOffset", 71 | "fePointLight", 72 | "feSpecularLighting", 73 | "feSpotLight", 74 | "feTile", 75 | "feTurbulence", 76 | ]; 77 | 78 | test("node test", () => { 79 | const svg = SVG(); 80 | Object.keys(NodeAndChildren).forEach(parent => { 81 | const node = svg[parent](); 82 | NodeAndChildren[parent].forEach(child => node[child]()); 83 | expect(node.childNodes.length).toBe(NodeAndChildren[parent].length); 84 | NodeAndChildren[parent].forEach((child, i) => expect(node.childNodes[i].nodeName) 85 | .toBe(child)); 86 | }); 87 | expect(svg.childNodes.length).toBe(Object.keys(NodeAndChildren).length); 88 | Object.keys(NodeAndChildren).forEach((child, i) => expect(svg.childNodes[i].nodeName) 89 | .toBe(child)); 90 | }); 91 | 92 | test("node names", () => { 93 | const createElements = NodesNames.map(nodeName => SVG[nodeName]()) 94 | .map((node, i) => node.nodeName === NodesNames[i]) 95 | .reduce((a, b) => a && b, true); 96 | expect(createElements).toBe(true); 97 | }); 98 | -------------------------------------------------------------------------------- /tests/object.assign.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | 3 | const test1 = { 4 | a: { 5 | one: { 6 | what: true, 7 | }, 8 | two: { 9 | whatagain: true, 10 | }, 11 | }, 12 | b: { 13 | a: { 14 | what: true, 15 | }, 16 | }, 17 | }; 18 | 19 | const test2 = { 20 | a: { 21 | three: { 22 | hi: true, 23 | }, 24 | four: { 25 | whatagain: true, 26 | }, 27 | }, 28 | b: { 29 | b: { 30 | okay: true, 31 | }, 32 | }, 33 | }; 34 | 35 | test("object assign", () => { 36 | Object.assign(test1, test2); 37 | 38 | expect(test1.a.one == null).toBe(true); 39 | expect(test1.a.two == null).toBe(true); 40 | expect(test1.a.three != null).toBe(true); 41 | expect(test1.a.four != null).toBe(true); 42 | expect(test1.b.a == null).toBe(true); 43 | expect(test1.b.b != null).toBe(true); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/path.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("path", () => { 8 | const p = SVG.path(); 9 | p.Move(20, 20); 10 | expect(p.getAttribute("d")).toBe("M20 20"); 11 | p.line(50, 50); 12 | expect(p.getAttribute("d")).toBe("M20 20l50 50"); 13 | p.vertical(30); 14 | expect(p.getAttribute("d")).toBe("M20 20l50 50v30"); 15 | p.Curve(50, 0, 0, 50, 10, 10); 16 | expect(p.getAttribute("d")).toBe("M20 20l50 50v30C50 0 0 50 10 10"); 17 | p.clear(); 18 | // specification around getAttribute when it doesn't exist is "" or null 19 | expect(p.getAttribute("d") === "" || p.getAttribute("d") === null).toBe(true); 20 | }); 21 | 22 | const path_commands = [ 23 | "m", // move 24 | "l", // line 25 | "v", // vertical 26 | "h", // horizontal 27 | "a", // ellipse 28 | "c", // curve 29 | "s", // smoothCurve 30 | "q", // quadCurve 31 | "t", // smoothQuadCurve 32 | "z", // close 33 | ]; 34 | 35 | test("path init from args", () => { 36 | const pathString = "M20 40V60l10 10"; 37 | expect(SVG.path(pathString).getAttribute("d")).toBe(pathString); 38 | // expect(SVG.path({command:"M", values: [20, 40]}).getAttribute("d")).toBe("M20 40"); 39 | }); 40 | 41 | test("path commands", () => { 42 | const pathString = "M20 40V60l10 10"; 43 | const path = SVG.path(pathString); 44 | path.Line(50, 50); 45 | expect(path.getAttribute("d")).toBe(`${pathString}L50 50`); 46 | 47 | const commands = path.getCommands(); 48 | const expected = [ 49 | { command: "M", values: [20, 40] }, 50 | { command: "V", values: [60] }, 51 | { command: "l", values: [10, 10] }, 52 | { command: "L", values: [50, 50] }, 53 | ]; 54 | expected.forEach((el, i) => { 55 | expect(commands[i].command).toBe(expected[i].command); 56 | expect(JSON.stringify(commands[i].values)) 57 | .toBe(JSON.stringify(expected[i].values)); 58 | }); 59 | 60 | // path.add("H20V60"); 61 | // expect(path.getAttribute("d")).toBe(pathString + "L50 50" + "H20V60"); 62 | 63 | // path.set("H20V60"); 64 | // expect(path.getAttribute("d")).toBe("H20V60"); 65 | }); 66 | 67 | test("path commands", () => { 68 | const path = SVG.path("M50 50h200"); 69 | expect(path.getD()).toBe("M50 50h200"); 70 | const path2 = SVG.path(); 71 | expect(path2.getD()).toBe(""); 72 | }); 73 | 74 | test("path commands", () => { 75 | const path = SVG.path("M50 50"); 76 | path.addCommand("L", 100, 100); 77 | expect(path.getAttribute("d")).toBe("M50 50L100 100"); 78 | path.appendCommand("V", 66); 79 | expect(path.getAttribute("d")).toBe("M50 50L100 100V66"); 80 | path.clear(); 81 | expect(path.getAttribute("d") === null || path.getAttribute("d") === "").toBe(true); 82 | }); 83 | 84 | // test("bezier", () => { 85 | // let bez = SVG.bezier(0, 0, 25, 75, 75, 25, 100, 100); 86 | // const d = Array.from(bez.attributes).filter(a => a.nodeName === "d").shift(); 87 | // expect(d.nodeValue).toBe("M 0,0 C 25,75 75,25 100,100"); 88 | // bez.setBezier(0, 0, 100, 0, 100, 100, 0, 100); 89 | // const d2 = Array.from(bez.attributes).filter(a => a.nodeName === "d").shift(); 90 | // expect(d2.nodeValue).toBe("M 0,0 C 100,0 100,100 0,100"); 91 | // expect(true).toBe(true); 92 | // }); 93 | -------------------------------------------------------------------------------- /tests/polygon.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("argument parsing, polygon", () => { 8 | let poly = SVG.polygon([0, 0, 0, 1, 1, 1, 1, 0]); 9 | expect(poly.getAttribute("points")).toBe("0,0 0,1 1,1 1,0"); 10 | const polygons = [ 11 | SVG.polygon(0, 1, 2, 3, 4, 5), 12 | SVG.polygon([0, 1], [2, 3], [4, 5]), 13 | SVG.polygon([[0, 1], [2, 3], [4, 5]]), 14 | SVG.polygon([[0, 1]], [[2, 3]], [[4, 5]]), 15 | SVG.polygon({ x: 0, y: 1 }, { x: 2, y: 3 }, { x: 4, y: 5 }), 16 | SVG.polygon([{ x: 0, y: 1 }, { x: 2, y: 3 }, { x: 4, y: 5 }]), 17 | SVG.polygon([0, 1, 9], [2, 3, 9], [4, 5, 9]), 18 | SVG.polygon([[0, 1, 9], [2, 3, 9], [4, 5, 9]]), 19 | SVG.polygon([[0, 1, 9]], [[2, 3, 9]], [[4, 5, 9]]), 20 | SVG.polygon({ x: 0, y: 1, z: 9 }, { x: 2, y: 3, z: 9 }, { x: 4, y: 5, z: 9 }), 21 | // SVG.polygon([{x:0, y:1}], [{x:2, y:3}], [{x:4, y:5}]), 22 | // SVG.polygon([[{x:0, y:1}], [{x:2, y:3}], [{x:4, y:5}]]), 23 | ]; 24 | const result = polygons 25 | .map(p => p.getAttribute("points")) 26 | .map(points => points === "0,1 2,3 4,5") 27 | .reduce((a, b) => a && b, true); 28 | expect(result).toBe(true); 29 | }); 30 | 31 | test("polygon point methods", () => { 32 | const poly = SVG.polygon([0, 0, 0, 1, 1, 1, 1, 0]); 33 | expect(poly.getAttribute("points")).toBe("0,0 0,1 1,1 1,0"); 34 | poly.addPoint(6, 7); 35 | expect(poly.getAttribute("points")).toBe("0,0 0,1 1,1 1,0 6,7"); 36 | poly.addPoint([3, 4]); 37 | expect(poly.getAttribute("points")).toBe("0,0 0,1 1,1 1,0 6,7 3,4"); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/rect.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("rect", () => { 8 | const rect = SVG.rect(1, 2, 3, 4); 9 | expect(rect.getAttribute("x")).toBe("1"); 10 | expect(rect.getAttribute("y")).toBe("2"); 11 | expect(rect.getAttribute("width")).toBe("3"); 12 | expect(rect.getAttribute("height")).toBe("4"); 13 | 14 | const rect2 = SVG.rect(3, 4); 15 | expect(rect2.getAttribute("y") === null || rect2.getAttribute("y") === "") 16 | .toBe(true); 17 | expect(rect2.getAttribute("y") === null || rect2.getAttribute("y") === "") 18 | .toBe(true); 19 | expect(rect2.getAttribute("width")).toBe("3"); 20 | expect(rect2.getAttribute("height")).toBe("4"); 21 | }); 22 | 23 | test("rect, args, negative width height", () => { 24 | const rect = SVG.rect(300, 200, -300, -200); 25 | expect(rect.getAttribute("x")).toBe("0"); 26 | expect(rect.getAttribute("y")).toBe("0"); 27 | expect(rect.getAttribute("width")).toBe("300"); 28 | expect(rect.getAttribute("height")).toBe("200"); 29 | const rect2 = SVG.rect(300, 200, -320, -220); 30 | expect(rect2.getAttribute("x")).toBe("-20"); 31 | expect(rect2.getAttribute("y")).toBe("-20"); 32 | expect(rect2.getAttribute("width")).toBe("320"); 33 | expect(rect2.getAttribute("height")).toBe("220"); 34 | const rect3 = SVG.rect(300, 200, 320, -220); 35 | expect(rect3.getAttribute("x")).toBe("300"); 36 | expect(rect3.getAttribute("y")).toBe("-20"); 37 | expect(rect3.getAttribute("width")).toBe("320"); 38 | expect(rect3.getAttribute("height")).toBe("220"); 39 | const rect4 = SVG.rect(300, 200, -320, 220); 40 | expect(rect4.getAttribute("x")).toBe("-20"); 41 | expect(rect4.getAttribute("y")).toBe("200"); 42 | expect(rect4.getAttribute("width")).toBe("320"); 43 | expect(rect4.getAttribute("height")).toBe("220"); 44 | const rect5 = SVG.rect(-320, -220); 45 | expect(rect5.getAttribute("x")).toBe("-320"); 46 | expect(rect5.getAttribute("y")).toBe("-220"); 47 | expect(rect5.getAttribute("width")).toBe("320"); 48 | expect(rect5.getAttribute("height")).toBe("220"); 49 | const rect6 = SVG.rect(-320, 220); 50 | expect(rect6.getAttribute("x")).toBe("-320"); 51 | expect(rect6.getAttribute("y") === null || rect6.getAttribute("y") === "") 52 | .toBe(true); 53 | expect(rect6.getAttribute("width")).toBe("320"); 54 | expect(rect6.getAttribute("height")).toBe("220"); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/stylesheet.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("style", () => { 8 | const styleString = "line{stroke:purple};"; 9 | const svg = SVG(); 10 | svg.stylesheet(styleString); 11 | const style = Array.from(svg.childNodes).filter(a => a.nodeName === "style").shift(); 12 | expect(style.childNodes[0].textContent).toBe(styleString); 13 | }); 14 | 15 | test("style setTextContent", () => { 16 | const styleString = "line{stroke:purple};"; 17 | const styleString2 = "circle { fill: '#000' }"; 18 | const svg = SVG(); 19 | const stylesheet = svg.stylesheet(styleString); 20 | stylesheet.setTextContent(styleString2); 21 | const style = Array.from(svg.childNodes).filter(a => a.nodeName === "style").shift(); 22 | expect(style.childNodes[0].textContent).toBe(styleString2); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/svg.animation.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("animation", () => { 8 | const svg = SVG(); 9 | svg.play = e => {}; 10 | svg.stop(); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/svg.args.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | const { DOMParser } = xmldom; 7 | 8 | test("argument parsing, svg", () => { 9 | const svg0 = SVG(); 10 | const vb0 = svg0.getAttribute("viewBox"); 11 | expect(vb0 == null || vb0 === "").toBe(true); 12 | 13 | const svg1 = SVG(400, 500); 14 | expect(svg1.getAttribute("viewBox")).toBe("0 0 400 500"); 15 | 16 | const svg2 = SVG(1, 2, 400, 500); 17 | expect(svg2.getAttribute("viewBox")).toBe("1 2 400 500"); 18 | 19 | const svg3 = SVG(1, 400, 500); 20 | const vb3 = svg3.getAttribute("viewBox"); 21 | expect(vb3 == null || vb3 === "").toBe(true); 22 | }); 23 | 24 | test("no parent", () => { 25 | const svg = SVG(600, 600); 26 | expect(svg.parentNode).toBe(null); 27 | }); 28 | 29 | test("parent element", () => { 30 | const parent = (new DOMParser()).parseFromString("
", "text/xml").documentElement; 31 | const svg = SVG(600, 600, parent); 32 | expect(svg.parentNode.nodeName).toBe("div"); 33 | }); 34 | 35 | // test("svg embed as string", () => { 36 | // // this string contains \n newlines, the load method targets and removes them 37 | // // by testing the element at [0] this is also testing the newline removal 38 | // const svgString = ` 39 | // 40 | // `; 41 | // const svg = SVG(svgString); 42 | // expect(svg.childNodes.length).toBe(1); 43 | // expect(svg.childNodes[0].nodeName).toBe("line"); 44 | // }); 45 | -------------------------------------------------------------------------------- /tests/svg.background.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("set background", () => { 8 | const svg = SVG(); 9 | svg.background("black", true); 10 | svg.background("#332698", false); 11 | expect(svg.childNodes.length).toBe(1); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/svg.controls.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("deprecated", () => expect(true).toBe(true)); 8 | 9 | // test("controls", () => { 10 | // const svg = SVG(); 11 | // const controlLayer = svg.g(); 12 | // svg.controls(3) 13 | // .svg(() => SVG.circle(svg.getWidth() * 0.1).fill("gray")) 14 | // .position(() => [svg.getWidth() * 0.5, svg.getHeight() * 0.5]) 15 | // .parent(controlLayer) 16 | // .onChange((point) => { 17 | // point.svg.setRadius(Math.random() * 10); 18 | // }, true); 19 | // }); 20 | -------------------------------------------------------------------------------- /tests/svg.load.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("", () => expect(true).toBe(true)); 8 | 9 | // const path = "./tests/dragon.svg"; 10 | 11 | // const testSVG = ` 12 | // 13 | // `; 14 | 15 | // test("loading callback", done => { 16 | // SVG(400, 400, () => { 17 | // done(); 18 | // }); 19 | // }); 20 | 21 | // // test("async attempt promises", () => { 22 | // // const svg = SVG(); 23 | // // svg.load(path) 24 | // // .then(() => {}); 25 | // // expect(svg.childNodes.length).toBe(0); 26 | // // }); 27 | 28 | // test("async using fs", (done) => { 29 | // const svg = SVG(); 30 | // fs.readFile(path, { encoding: "utf8" }, (err, data) => { 31 | // svg.load(data); 32 | // const polyline = Array.from(svg.childNodes) 33 | // .filter(a => a.nodeName === "polyline") 34 | // .shift(); 35 | // expect(polyline !== undefined).toBe(true); 36 | // done(); 37 | // }); 38 | // }); 39 | 40 | // test("async attempt with node", () => { 41 | // const svg = SVG(); 42 | // svg.load(path); 43 | // expect(svg.childNodes.length).toBe(0); 44 | // }); 45 | 46 | // test("sync", () => { 47 | // const svg = SVG(); 48 | // svg.load(testSVG); 49 | // const circle = Array.from(svg.childNodes) 50 | // .filter(a => a.nodeName === "circle") 51 | // .shift(); 52 | 53 | // expect(circle !== undefined).toBe(true); 54 | // }); 55 | 56 | // test("import load()", () => { 57 | // const svgString = ``; 58 | 59 | // const svgFromString = SVG(); 60 | // expect(svgFromString.childNodes.length).toBe(0); 61 | // svgFromString.load(svgString); 62 | // expect(svgFromString.childNodes.length).toBe(1); 63 | 64 | // // console.log("svgFromString.childNodes", svgFromString.childNodes); 65 | // // console.log("window", window); 66 | // // console.log("window.document", window.document); 67 | // }); 68 | 69 | // test("import string in argument", () => { 70 | // const svgString = ``; 71 | 72 | // const svg = SVG(svgString); 73 | // expect(svg.childNodes.length).toBe(1); 74 | 75 | // // console.log("svg.childNodes", svg.childNodes); 76 | // // console.log("window", window); 77 | // // console.log("window.document", window.document); 78 | // }); 79 | 80 | // test("import string in argument with attributes", () => { 81 | // const svgString = ``; 82 | // const svg = SVG(svgString); 83 | // expect(svg.getAttribute("viewBox")).toBe("10 20 800 600"); 84 | // expect(svg.getAttribute("display")).toBe("none"); 85 | // }); 86 | -------------------------------------------------------------------------------- /tests/svg.save.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("", () => expect(true).toBe(true)); 8 | 9 | // test("export options", () => { 10 | // const svg = SVG(); 11 | // const save0 = svg.save(); 12 | // const save1 = svg.save({ output: "string" }); 13 | // const save2 = svg.save({ output: "svg" }); 14 | // const save3 = svg.save({ windowStyle: true }); 15 | // expect(typeof save0).toBe("string"); 16 | // expect(typeof save1).toBe("string"); 17 | // expect(typeof save2).toBe("object"); 18 | // expect(typeof save3).toBe("string"); 19 | // }); 20 | 21 | // test("svg export", () => { 22 | // const svg = SVG(); 23 | // svg.line(0, 0, 300, 150).stroke("black").strokeWidth(5); 24 | // const asString = svg.save(); 25 | // const asSvg = svg.save({ output: "svg" }); 26 | // const expectedString = ` 27 | // 28 | // `; 29 | // // const expectedString = ``; 30 | // expect(asString).toBe(expectedString); 31 | // expect(asSvg.childNodes.length).toBe(1); 32 | // expect(asSvg.childNodes[0].nodeName).toBe("line"); 33 | // }); 34 | 35 | // test("svg export with comments", () => { 36 | // const svgString = ` 37 | // 38 | // 39 | // 40 | // `; 41 | // const svg = SVG(svgString); 42 | // const asSvg = svg.save({ output: "svg" }); 43 | // expect(asSvg.childNodes.length).toBe(3); 44 | // expect(asSvg.childNodes[0].nodeName).toBe("#comment"); 45 | // expect(asSvg.childNodes[1].nodeName).toBe("line"); 46 | // expect(asSvg.childNodes[2].nodeName).toBe("#comment"); 47 | // }); 48 | -------------------------------------------------------------------------------- /tests/svg.viewbox.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("viewBox get and set", () => { 8 | const svg = SVG(); 9 | svg.setViewBox(1, 2, 3, 4); 10 | expect(svg.getAttribute("viewBox")).toBe("1 2 3 4"); 11 | svg.setViewBox("-10 -10 400 500"); 12 | expect(svg.getAttribute("viewBox")).toBe("-10 -10 400 500"); 13 | svg.setViewBox(300, 200); 14 | expect(svg.getAttribute("viewBox")).toBe("0 0 300 200"); 15 | }); 16 | 17 | test("get width and height", () => { 18 | const svg = SVG(); 19 | expect(svg.getWidth()).toBe(undefined); 20 | expect(svg.getHeight()).toBe(undefined); 21 | const svg2 = SVG(400, 300); 22 | expect(svg2.getWidth()).toBe(400); 23 | expect(svg2.getHeight()).toBe(300); 24 | const svg3 = SVG(0, 0, 800, 600); 25 | expect(svg3.getWidth()).toBe(800); 26 | expect(svg3.getHeight()).toBe(600); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/transforms.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("parseTransform", () => { 8 | const transformString = "translate(20 100) rotate(45) scale(2 1) matrix(0 -1 1 0 0 0)"; 9 | const result = SVG.parseTransform(transformString); 10 | ["translate", "rotate", "scale", "matrix"] 11 | .forEach((name, i) => expect(result[i].transform).toBe(name)); 12 | }); 13 | 14 | test("transformStringToMatrix", () => { 15 | const transformString = "translate(20 100) rotate(45) scale(2 1) matrix(0 -1 1 0 0 0)"; 16 | expect(SVG.transformStringToMatrix(transformString).length).toBe(6); 17 | 18 | const transformString2 = "translate(20) skewX(4) rotate(45 2 3) skewY(2) scale(2)"; 19 | expect(SVG.transformStringToMatrix(transformString2).length).toBe(6); 20 | }); 21 | 22 | test("transformStringToMatrix bad input", () => { 23 | const transformString = "translate() rotate() scale() skewX() skewY()"; 24 | expect(SVG.transformStringToMatrix(transformString).length).toBe(6); 25 | }); 26 | 27 | test("transforms", () => { 28 | const svg = SVG(); 29 | 30 | const transformString = "translate(20 100) rotate(45) translate(50 50) matrix(0 -1 1 0 0 0)"; 31 | 32 | ["svg", "g", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect"] 33 | // , "text" 34 | .map(node => svg[node]() 35 | .translate("20 100") 36 | .rotate(45) 37 | .translate(50, 50) 38 | .matrix(0, -1, 1, 0, 0, 0)) 39 | .forEach(p => expect(p.getAttribute("transform")) 40 | .toBe(transformString)); 41 | }); 42 | 43 | test("clear transform", () => { 44 | const svg = SVG(); 45 | const l = svg.line(0, 0, 400, 400) 46 | .rotate(45) 47 | .translate(50, 50) 48 | .clearTransform(); 49 | const transform = l.getAttribute("transform"); 50 | expect(transform == null || transform === "").toBe(true); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/types.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | const primitives = [ 8 | "line", 9 | "circle", 10 | "ellipse", 11 | "rect", 12 | "polygon", 13 | "polyline", 14 | "text", 15 | ]; 16 | 17 | const groupLevel = ["g"]; 18 | 19 | const defsLevel = [ 20 | "style", 21 | "clipPath", 22 | "mask", 23 | "script", 24 | ]; 25 | 26 | const rootLevel = [ 27 | "defs", 28 | "style", 29 | // "clipPath", // conflict, clipPath is a constructor AND an assigner 30 | // "mask", 31 | ]; 32 | 33 | const customPrimitives = [ 34 | "bezier", 35 | "wedge", 36 | "arc", 37 | "parabola", 38 | "regularPolygon", 39 | "arrow", 40 | ]; 41 | 42 | test("svg and group", () => { 43 | const svg = SVG(); 44 | primitives.forEach(p => expect(typeof svg[p]).toBe("function")); 45 | groupLevel.forEach(g => expect(typeof svg[g]).toBe("function")); 46 | rootLevel.forEach(r => expect(typeof svg[r]).toBe("function")); 47 | 48 | const group = SVG.g(); 49 | primitives.forEach(p => expect(typeof group[p]).toBe("function")); 50 | groupLevel.forEach(g => expect(typeof group[g]).toBe("function")); 51 | rootLevel.forEach(r => expect(typeof group[r]).not.toBe("function")); 52 | 53 | const defs = SVG.defs(); 54 | // primitives.forEach(p => expect(typeof defs[p]).toBe("function")); 55 | // groupLevel.forEach(g => expect(typeof defs[g]).toBe("function")); 56 | // defsLevel.forEach(r => expect(typeof defs[r]).toBe("function")); 57 | }); 58 | -------------------------------------------------------------------------------- /tests/urls.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("custom id names", () => { 8 | ["clipPath", "symbol", "mask", "marker"].forEach(nodeName => { 9 | const svg = SVG(); 10 | const test1 = svg[nodeName](); 11 | const test2 = svg[nodeName]("what is"); 12 | expect(test1.getAttribute("id").length).toBe(5); 13 | expect(test2.getAttribute("id")).toBe("what is"); 14 | }); 15 | }); 16 | 17 | test("assign to types", () => { 18 | const svg = SVG(); 19 | const things = ["clipPath", "symbol", "mask", "marker", "marker", "marker"] 20 | .map(nodeName => svg[nodeName]()); 21 | const line = svg.line(1, 2, 3, 4); 22 | ["clipPath", "mask", "symbol", "markerEnd", "markerMid", "markerStart"] 23 | .forEach((method, i) => line[method](things[i])); 24 | 25 | // these should be the attributes 26 | ["x1", "y1", "x2", "y2", "clip-path", "mask", "symbol", "marker-end", "marker-mid", "marker-start"] 27 | .forEach((attr, i) => expect(line.attributes[i].name).toBe(attr)); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/use.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | // const ear = require("./ear"); 7 | 8 | test("", () => expect(true).toBe(true)); 9 | 10 | // test("use with rabbit ear", () => { 11 | // ear.use(SVG); 12 | // expect(typeof ear.segment.svg).toBe("function"); 13 | // expect(typeof ear.segment.svg).toBe("function"); 14 | // expect(typeof ear.circle.svg).toBe("function"); 15 | // expect(typeof ear.ellipse.svg).toBe("function"); 16 | // expect(typeof ear.rect.svg).toBe("function"); 17 | // expect(typeof ear.polygon.svg).toBe("function"); 18 | // ear.segment.svg(); 19 | // ear.circle.svg(); 20 | // ear.ellipse.svg(); 21 | // ear.rect.svg(); 22 | // ear.polygon.svg(); 23 | // }); 24 | -------------------------------------------------------------------------------- /tests/window.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import xmldom from "@xmldom/xmldom"; 3 | import SVG from "../src/index.js"; 4 | 5 | SVG.window = xmldom; 6 | 7 | test("environment", () => { 8 | expect(true).toBe(true); 9 | }); 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "exclude": ["src/primitives/**/*", "src/sketches/**/*"], 4 | "compilerOptions": { 5 | // not sure if these should be here 6 | "target": "ES2015", 7 | // "module": "es2015", 8 | "lib": ["es2015", "DOM"], 9 | // Tells TypeScript to read JS files, as 10 | // normally they are ignored as source files 11 | "allowJs": true, 12 | "checkJs": true, 13 | // Generate d.ts files 14 | "declaration": true, 15 | // This compiler run should 16 | // only output d.ts files 17 | "emitDeclarationOnly": true, 18 | // Types should go into this directory. 19 | // Removing this would place the .d.ts files 20 | // next to the .js files 21 | "outDir": "types" 22 | // go to js file when using IDE functions like 23 | // "Go to Definition" in VSCode 24 | // "declarationMap": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /types/arguments/makeCoordinates.d.ts: -------------------------------------------------------------------------------- 1 | export default makeCoordinates; 2 | /** 3 | * this will extract coordinates from a set of inputs 4 | * and present them as a stride-2 flat array. length % 2 === 0 5 | * a 1D array of numbers, alternating x y 6 | * 7 | * use flatten() everytime you call this! 8 | * it's necessary the entries sit at the top level of ...args 9 | * findCoordinates(...flatten(...args)); 10 | */ 11 | declare function makeCoordinates(...args: any[]): any[]; 12 | -------------------------------------------------------------------------------- /types/arguments/makeViewBox.d.ts: -------------------------------------------------------------------------------- 1 | export default makeViewBox; 2 | /** 3 | * @returns {string | undefined} 4 | */ 5 | declare function makeViewBox(...args: any[]): string | undefined; 6 | -------------------------------------------------------------------------------- /types/arguments/semiFlattenArrays.d.ts: -------------------------------------------------------------------------------- 1 | export function svgSemiFlattenArrays(...args: any[]): any[][]; 2 | export default svgSemiFlattenArrays; 3 | -------------------------------------------------------------------------------- /types/colors/convert.d.ts: -------------------------------------------------------------------------------- 1 | export function hslToRgb(hue: number, saturation: number, lightness: number, alpha: number | undefined): number[]; 2 | export function hexToRgb(string: string): number[]; 3 | export function rgbToHex(red: number, green: number, blue: number, alpha: number | undefined): string; 4 | -------------------------------------------------------------------------------- /types/colors/parseColor.d.ts: -------------------------------------------------------------------------------- 1 | export function parseColorToRgb(string: string): number[] | undefined; 2 | export function parseColorToHex(string: string): string; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/arc/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace arc { 3 | export { str_path as nodeName }; 4 | export let attributes: string[]; 5 | export { arcArguments as args }; 6 | export let methods: { 7 | clearTransform: (el: any) => any; 8 | setArc: (el: any, ...args: any[]) => any; 9 | }; 10 | } 11 | } 12 | export default _default; 13 | import { str_path } from "../../../environment/strings.js"; 14 | declare function arcArguments(a: any, b: any, c: any, d: any, e: any): string[]; 15 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace arrow { 3 | export let nodeName: string; 4 | export let attributes: any[]; 5 | export function args(): any[]; 6 | export { ArrowMethods as methods }; 7 | export { init }; 8 | } 9 | } 10 | export default _default; 11 | import ArrowMethods from "./methods.js"; 12 | import init from "./init.js"; 13 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/init.d.ts: -------------------------------------------------------------------------------- 1 | export default init; 2 | declare function init(...args: any[]): any; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/makeArrowPaths.d.ts: -------------------------------------------------------------------------------- 1 | export default makeArrowPaths; 2 | /** 3 | * @description 4 | * @param {{ 5 | * points: [number, number, number, number], 6 | * padding: number, 7 | * bend: number, 8 | * pinch: number, 9 | * }} options 10 | */ 11 | declare function makeArrowPaths(options: { 12 | points: [number, number, number, number]; 13 | padding: number; 14 | bend: number; 15 | pinch: number; 16 | }): { 17 | line: string; 18 | tail: string; 19 | head: string; 20 | }; 21 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/methods.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: { 2 | clearTransform: (el: any) => any; 3 | setPoints: (element: any, ...args: any[]) => any; 4 | points: (element: any, ...args: any[]) => any; 5 | bend: (element: any, amount: any) => any; 6 | pinch: (element: any, amount: any) => any; 7 | padding: (element: any, amount: any) => any; 8 | head: (element: any, options: any) => any; 9 | tail: (element: any, options: any) => any; 10 | getLine: (element: any) => any; 11 | getHead: (element: any) => any; 12 | getTail: (element: any) => any; 13 | }; 14 | export default _default; 15 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/options.d.ts: -------------------------------------------------------------------------------- 1 | export function makeArrowOptions(): { 2 | head: { 3 | visible: boolean; 4 | width: number; 5 | height: number; 6 | padding: number; 7 | }; 8 | tail: { 9 | visible: boolean; 10 | width: number; 11 | height: number; 12 | padding: number; 13 | }; 14 | bend: number; 15 | padding: number; 16 | pinch: number; 17 | points: any[]; 18 | }; 19 | -------------------------------------------------------------------------------- /types/constructor/extensions/arrow/template.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/SVG/4c1c4dd4ef29bb5d0e064794d685a405ad2524de/types/constructor/extensions/arrow/template.d.ts -------------------------------------------------------------------------------- /types/constructor/extensions/circle.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace circle { 3 | function args(a: any, b: any, c: any, d: any): any[]; 4 | let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | radius: (el: any, r: any) => any; 10 | setRadius: (el: any, r: any) => any; 11 | origin: (el: any, a: any, b: any) => any; 12 | setOrigin: (el: any, a: any, b: any) => any; 13 | center: (el: any, a: any, b: any) => any; 14 | setCenter: (el: any, a: any, b: any) => any; 15 | position: (el: any, a: any, b: any) => any; 16 | setPosition: (el: any, a: any, b: any) => any; 17 | }; 18 | } 19 | } 20 | export default _default; 21 | -------------------------------------------------------------------------------- /types/constructor/extensions/curve/arguments.d.ts: -------------------------------------------------------------------------------- 1 | export default curveArguments; 2 | declare function curveArguments(...args: any[]): string[]; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/curve/getCurveEndpoints.d.ts: -------------------------------------------------------------------------------- 1 | export default getCurveEndpoints; 2 | declare function getCurveEndpoints(d: any): any[]; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/curve/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace curve { 3 | export { str_path as nodeName }; 4 | export let attributes: string[]; 5 | export { args }; 6 | export { curve_methods as methods }; 7 | } 8 | } 9 | export default _default; 10 | import { str_path } from "../../../environment/strings.js"; 11 | import args from "./arguments.js"; 12 | import curve_methods from "./methods.js"; 13 | -------------------------------------------------------------------------------- /types/constructor/extensions/curve/makeCurvePath.d.ts: -------------------------------------------------------------------------------- 1 | export default makeCurvePath; 2 | declare function makeCurvePath(endpoints?: any[], bend?: number, pinch?: number): string; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/curve/methods.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: { 2 | clearTransform: (el: any) => any; 3 | setPoints: (element: any, ...args: any[]) => any; 4 | bend: (element: any, amount: any) => any; 5 | pinch: (element: any, amount: any) => any; 6 | }; 7 | export default _default; 8 | -------------------------------------------------------------------------------- /types/constructor/extensions/ellipse.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace ellipse { 3 | function args(a: any, b: any, c: any, d: any): any[]; 4 | let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | radius: (el: any, rx: any, ry: any) => any; 10 | setRadius: (el: any, rx: any, ry: any) => any; 11 | origin: (el: any, a: any, b: any) => any; 12 | setOrigin: (el: any, a: any, b: any) => any; 13 | center: (el: any, a: any, b: any) => any; 14 | setCenter: (el: any, a: any, b: any) => any; 15 | position: (el: any, a: any, b: any) => any; 16 | setPosition: (el: any, a: any, b: any) => any; 17 | }; 18 | } 19 | } 20 | export default _default; 21 | -------------------------------------------------------------------------------- /types/constructor/extensions/g.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace g { 3 | let methods: { 4 | removeChildren: (element: any) => any; 5 | appendTo: (element: any, parent: any) => any; 6 | setAttributes: (element: any, attrs: any) => any; 7 | clearTransform: (el: any) => any; 8 | }; 9 | } 10 | } 11 | export default _default; 12 | -------------------------------------------------------------------------------- /types/constructor/extensions/line.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace line { 3 | export { Args as args }; 4 | export let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | setPoints: (element: any, ...args: any[]) => any; 10 | }; 11 | } 12 | } 13 | export default _default; 14 | declare function Args(...args: any[]): any[]; 15 | -------------------------------------------------------------------------------- /types/constructor/extensions/maskTypes.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace mask { 3 | export { maskArgs as args }; 4 | export let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | }; 10 | } 11 | namespace clipPath { 12 | export { maskArgs as args }; 13 | let methods_1: { 14 | removeChildren: (element: any) => any; 15 | appendTo: (element: any, parent: any) => any; 16 | setAttributes: (element: any, attrs: any) => any; 17 | clearTransform: (el: any) => any; 18 | }; 19 | export { methods_1 as methods }; 20 | } 21 | namespace symbol { 22 | export { maskArgs as args }; 23 | let methods_2: { 24 | removeChildren: (element: any) => any; 25 | appendTo: (element: any, parent: any) => any; 26 | setAttributes: (element: any, attrs: any) => any; 27 | clearTransform: (el: any) => any; 28 | }; 29 | export { methods_2 as methods }; 30 | } 31 | namespace marker { 32 | export { maskArgs as args }; 33 | let methods_3: { 34 | removeChildren: (element: any) => any; 35 | appendTo: (element: any, parent: any) => any; 36 | setAttributes: (element: any, attrs: any) => any; 37 | clearTransform: (el: any) => any; 38 | size: (element: any, ...args: any[]) => any; 39 | setViewBox: (element: any, ...args: any[]) => any; 40 | }; 41 | export { methods_3 as methods }; 42 | } 43 | } 44 | export default _default; 45 | declare function maskArgs(...args: any[]): any[]; 46 | -------------------------------------------------------------------------------- /types/constructor/extensions/origami/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace origami { 3 | export let nodeName: string; 4 | export { init }; 5 | export function args(): any[]; 6 | export { methods }; 7 | } 8 | } 9 | export default _default; 10 | import init from "./init.js"; 11 | import methods from "./methods.js"; 12 | -------------------------------------------------------------------------------- /types/constructor/extensions/origami/init.d.ts: -------------------------------------------------------------------------------- 1 | export default init; 2 | declare function init(graph: any, ...args: any[]): any; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/origami/methods.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: { 2 | removeChildren: (element: any) => any; 3 | appendTo: (element: any, parent: any) => any; 4 | setAttributes: (element: any, attrs: any) => any; 5 | clearTransform: (el: any) => any; 6 | }; 7 | export default _default; 8 | -------------------------------------------------------------------------------- /types/constructor/extensions/path.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace path { 3 | export { path_methods as methods }; 4 | } 5 | } 6 | export default _default; 7 | declare const path_methods: { 8 | removeChildren: (element: any) => any; 9 | appendTo: (element: any, parent: any) => any; 10 | setAttributes: (element: any, attrs: any) => any; 11 | clearTransform: (el: any) => any; 12 | addCommand: (el: any, command: any, ...args: any[]) => any; 13 | appendCommand: (el: any, command: any, ...args: any[]) => any; 14 | clear: (element: any) => any; 15 | getCommands: (element: any) => { 16 | command: string; 17 | values: number[]; 18 | }[]; 19 | get: (element: any) => { 20 | command: string; 21 | values: number[]; 22 | }[]; 23 | getD: (el: any) => any; 24 | }; 25 | -------------------------------------------------------------------------------- /types/constructor/extensions/polys.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace polyline { 3 | export { Args as args }; 4 | export let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | setPoints: (element: any, ...args: any[]) => any; 10 | addPoint: (element: any, ...args: any[]) => any; 11 | }; 12 | } 13 | namespace polygon { 14 | export { Args as args }; 15 | let methods_1: { 16 | removeChildren: (element: any) => any; 17 | appendTo: (element: any, parent: any) => any; 18 | setAttributes: (element: any, attrs: any) => any; 19 | clearTransform: (el: any) => any; 20 | setPoints: (element: any, ...args: any[]) => any; 21 | addPoint: (element: any, ...args: any[]) => any; 22 | }; 23 | export { methods_1 as methods }; 24 | } 25 | } 26 | export default _default; 27 | declare function Args(...args: any[]): any[]; 28 | -------------------------------------------------------------------------------- /types/constructor/extensions/rect.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace rect { 3 | function args(a: any, b: any, c: any, d: any): any; 4 | let methods: { 5 | removeChildren: (element: any) => any; 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | origin: (el: any, a: any, b: any) => any; 10 | setOrigin: (el: any, a: any, b: any) => any; 11 | center: (el: any, a: any, b: any) => any; 12 | setCenter: (el: any, a: any, b: any) => any; 13 | size: (el: any, rx: any, ry: any) => any; 14 | setSize: (el: any, rx: any, ry: any) => any; 15 | }; 16 | } 17 | } 18 | export default _default; 19 | -------------------------------------------------------------------------------- /types/constructor/extensions/shared/dom.d.ts: -------------------------------------------------------------------------------- 1 | export function removeChildren(element: any): any; 2 | export function appendTo(element: any, parent: any): any; 3 | export function setAttributes(element: any, attrs: any): any; 4 | -------------------------------------------------------------------------------- /types/constructor/extensions/shared/makeArcPath.d.ts: -------------------------------------------------------------------------------- 1 | export default arcPath; 2 | declare function arcPath(x: any, y: any, radius: any, startAngle: any, endAngle: any, includeCenter?: boolean): string; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/shared/transforms.d.ts: -------------------------------------------------------------------------------- 1 | export default TransformMethods; 2 | declare namespace TransformMethods { 3 | function clearTransform(el: any): any; 4 | } 5 | -------------------------------------------------------------------------------- /types/constructor/extensions/shared/urls.d.ts: -------------------------------------------------------------------------------- 1 | export default methods; 2 | declare const methods: {}; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/style.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace style { 3 | function init(text: any): any; 4 | namespace methods { 5 | function setTextContent(el: any, text: any): any; 6 | } 7 | } 8 | } 9 | export default _default; 10 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/animation.d.ts: -------------------------------------------------------------------------------- 1 | export default Animation; 2 | declare function Animation(element: any): void; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/controls-rewrite.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/SVG/4c1c4dd4ef29bb5d0e064794d685a405ad2524de/types/constructor/extensions/svg/controls-rewrite.d.ts -------------------------------------------------------------------------------- /types/constructor/extensions/svg/controls.d.ts: -------------------------------------------------------------------------------- 1 | export default applyControlsToSVG; 2 | declare function applyControlsToSVG(svg: any): void; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/getSVGFrame.d.ts: -------------------------------------------------------------------------------- 1 | export default getSVGFrame; 2 | declare function getSVGFrame(element: any): any; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace svg { 3 | export function args(...args: any[]): string[]; 4 | export { methods }; 5 | export function init(...args: any[]): any; 6 | } 7 | } 8 | export default _default; 9 | import methods from "./methods.js"; 10 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/makeBackground.d.ts: -------------------------------------------------------------------------------- 1 | export default makeBackground; 2 | declare function makeBackground(element: any, color: any): any; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/methods.d.ts: -------------------------------------------------------------------------------- 1 | export function findOneElement(element: any, nodeName: any): any; 2 | declare const _default: { 3 | removeChildren: (element: any) => any; 4 | appendTo: (element: any, parent: any) => any; 5 | setAttributes: (element: any, attrs: any) => any; 6 | clearTransform: (el: any) => any; 7 | clear: (element: any) => any; 8 | size: (element: any, ...args: any[]) => any; 9 | setViewBox: (element: any, ...args: any[]) => any; 10 | getViewBox: (element: any) => any; 11 | padding: (element: any, padding: any) => any; 12 | background: (element: any, color: any) => any; 13 | getWidth: (el: any) => any; 14 | getHeight: (el: any) => any; 15 | stylesheet: (el: any, text: any) => any; 16 | }; 17 | export default _default; 18 | -------------------------------------------------------------------------------- /types/constructor/extensions/svg/touch.d.ts: -------------------------------------------------------------------------------- 1 | export default TouchEvents; 2 | declare function TouchEvents(element: any): void; 3 | -------------------------------------------------------------------------------- /types/constructor/extensions/text.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace text { 3 | function args(a: any, b: any, c: any): any[]; 4 | function init(a: any, b: any, c: any, d: any): any; 5 | let methods: { 6 | appendTo: (element: any, parent: any) => any; 7 | setAttributes: (element: any, attrs: any) => any; 8 | clearTransform: (el: any) => any; 9 | }; 10 | } 11 | } 12 | export default _default; 13 | -------------------------------------------------------------------------------- /types/constructor/extensions/wedge/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | namespace wedge { 3 | export { str_path as nodeName }; 4 | export { wedgeArguments as args }; 5 | export let attributes: string[]; 6 | export let methods: { 7 | clearTransform: (el: any) => any; 8 | setArc: (el: any, ...args: any[]) => any; 9 | }; 10 | } 11 | } 12 | export default _default; 13 | import { str_path } from "../../../environment/strings.js"; 14 | declare function wedgeArguments(a: any, b: any, c: any, d: any, e: any): string[]; 15 | -------------------------------------------------------------------------------- /types/constructor/index.d.ts: -------------------------------------------------------------------------------- 1 | export default Constructor; 2 | /** 3 | * @description This is the main constructor for the library which generates 4 | * SVGElements (DOM elements) using createElementNS in the svg namespace. 5 | * Additionally, this element will be bound with methods to operate on the 6 | * element itself, which do things like set an attribute value or 7 | * create a child of this object. 8 | * Using this constructor, this library has full support for all elements 9 | * in the SVG spec (I think so, double check me on this), additionally, 10 | * some custom elements, for example "arrow" which makes a few shapes under 11 | * a single group. So this library is highly extendable, you can write 12 | * your own "arrow" objects, see more inside this directory's subdirectories. 13 | * @param {string} name the name of the element, although, slightly abstracted 14 | * from the actual element name, like "line" for because it supports 15 | * custom elements, "arrow", which in turn will create a or etc.. 16 | * @param {object} parent the parent to append this new node as a child to. 17 | */ 18 | declare function Constructor(name: string, parent: object, ...initArgs: any[]): any; 19 | -------------------------------------------------------------------------------- /types/elements/constructors.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {{[key: string]: Function }} 3 | */ 4 | export const constructors: { 5 | [key: string]: Function; 6 | }; 7 | export function SVG(...args: any[]): any; 8 | -------------------------------------------------------------------------------- /types/environment/detect.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | export const isBrowser: boolean; 5 | export const isBackend: boolean; 6 | export const isWebWorker: boolean; 7 | -------------------------------------------------------------------------------- /types/environment/lib.d.ts: -------------------------------------------------------------------------------- 1 | export default lib; 2 | /** 3 | * Rabbit Ear (c) Kraft 4 | */ 5 | declare const lib: {}; 6 | -------------------------------------------------------------------------------- /types/environment/messages.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _default { 2 | let window: string; 3 | } 4 | export default _default; 5 | -------------------------------------------------------------------------------- /types/environment/strings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Rabbit Ear (c) Kraft 3 | */ 4 | export const str_class: "class"; 5 | export const str_function: "function"; 6 | export const str_undefined: "undefined"; 7 | export const str_boolean: "boolean"; 8 | export const str_number: "number"; 9 | export const str_string: "string"; 10 | export const str_object: "object"; 11 | export const str_svg: "svg"; 12 | export const str_path: "path"; 13 | export const str_id: "id"; 14 | export const str_style: "style"; 15 | export const str_viewBox: "viewBox"; 16 | export const str_transform: "transform"; 17 | export const str_points: "points"; 18 | export const str_stroke: "stroke"; 19 | export const str_fill: "fill"; 20 | export const str_none: "none"; 21 | export const str_arrow: "arrow"; 22 | export const str_head: "head"; 23 | export const str_tail: "tail"; 24 | -------------------------------------------------------------------------------- /types/environment/window.d.ts: -------------------------------------------------------------------------------- 1 | export function setWindow(newWindow: any): any; 2 | export default RabbitEarWindow; 3 | /** 4 | * @description get the "window" object, which should have 5 | * DOMParser, XMLSerializer, and document. 6 | */ 7 | declare function RabbitEarWindow(): any; 8 | -------------------------------------------------------------------------------- /types/general/algebra.d.ts: -------------------------------------------------------------------------------- 1 | export function svg_add2(a: [number, number], b: [number, number]): [number, number]; 2 | export function svg_sub2(a: [number, number], b: [number, number]): [number, number]; 3 | export function svg_scale2(a: [number, number], s: number): [number, number]; 4 | export function svg_magnitudeSq2(a: [number, number]): number; 5 | export function svg_magnitude2(a: [number, number]): number; 6 | export function svg_distanceSq2(a: [number, number], b: [number, number]): number; 7 | export function svg_distance2(a: [number, number], b: [number, number]): number; 8 | export function svg_polar_to_cart(a: number, d: number): [number, number]; 9 | export function svg_multiplyMatrices2(m1: number[], m2: number[]): number[]; 10 | -------------------------------------------------------------------------------- /types/general/cdata.d.ts: -------------------------------------------------------------------------------- 1 | export function makeCDATASection(text: string): CDATASection; 2 | -------------------------------------------------------------------------------- /types/general/dom.d.ts: -------------------------------------------------------------------------------- 1 | export function xmlStringToElement(input: string, mimeType?: string): Element | null; 2 | export function getRootParent(el: Element): Element; 3 | export function findElementTypeInParents(element: Element, nodeName: string): Element | null; 4 | export function addClass(el: Element, ...classes: string[]): void; 5 | export function flattenDomTree(el: Element | ChildNode): (Element | ChildNode)[]; 6 | export function flattenDomTreeWithStyle(element: Element | ChildNode, attributes?: object): { 7 | element: Element | ChildNode; 8 | attributes: object; 9 | }[]; 10 | -------------------------------------------------------------------------------- /types/general/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: { 2 | setViewBox: (element: any, ...args: any[]) => any; 3 | getViewBox: (element: any) => any; 4 | convertToViewBox: (svg: any, x: any, y: any) => any[]; 5 | foldToViewBox: ({ vertices_coords }: { 6 | vertices_coords: any; 7 | }) => string; 8 | parseTransform: (transform: string) => { 9 | transform: string; 10 | parameters: number[]; 11 | }[]; 12 | transformStringToMatrix: (string: any) => any; 13 | pathCommandNames: { 14 | m: string; 15 | l: string; 16 | v: string; 17 | h: string; 18 | a: string; 19 | c: string; 20 | s: string; 21 | q: string; 22 | t: string; 23 | z: string; 24 | }; 25 | parsePathCommands: (d: string) => { 26 | command: string; 27 | values: number[]; 28 | }[]; 29 | parsePathCommandsWithEndpoints: (d: any) => { 30 | end: any; 31 | start: any; 32 | command: string; 33 | values: number[]; 34 | }[]; 35 | makeCDATASection: (text: string) => CDATASection; 36 | xmlStringToElement: (input: string, mimeType?: string) => Element; 37 | getRootParent: (el: Element) => Element; 38 | findElementTypeInParents: (element: Element, nodeName: string) => Element; 39 | addClass: (el: Element, ...classes: string[]) => void; 40 | flattenDomTree: (el: Element | ChildNode) => (Element | ChildNode)[]; 41 | flattenDomTreeWithStyle: (element: Element | ChildNode, attributes?: any) => { 42 | element: Element | ChildNode; 43 | attributes: any; 44 | }[]; 45 | svg_add2: (a: [number, number], b: [number, number]) => [number, number]; 46 | svg_sub2: (a: [number, number], b: [number, number]) => [number, number]; 47 | svg_scale2: (a: [number, number], s: number) => [number, number]; 48 | svg_magnitudeSq2: (a: [number, number]) => number; 49 | svg_magnitude2: (a: [number, number]) => number; 50 | svg_distanceSq2: (a: [number, number], b: [number, number]) => number; 51 | svg_distance2: (a: [number, number], b: [number, number]) => number; 52 | svg_polar_to_cart: (a: number, d: number) => [number, number]; 53 | svg_multiplyMatrices2: (m1: number[], m2: number[]) => number[]; 54 | }; 55 | export default _default; 56 | -------------------------------------------------------------------------------- /types/general/path.d.ts: -------------------------------------------------------------------------------- 1 | export namespace pathCommandNames { 2 | let m: string; 3 | let l: string; 4 | let v: string; 5 | let h: string; 6 | let a: string; 7 | let c: string; 8 | let s: string; 9 | let q: string; 10 | let t: string; 11 | let z: string; 12 | } 13 | export function parsePathCommands(d: string): { 14 | command: string; 15 | values: number[]; 16 | }[]; 17 | export function parsePathCommandsWithEndpoints(d: any): { 18 | end: any; 19 | start: any; 20 | command: string; 21 | values: number[]; 22 | }[]; 23 | -------------------------------------------------------------------------------- /types/general/string.d.ts: -------------------------------------------------------------------------------- 1 | export function makeUUID(): string; 2 | export function toCamel(s: string): string; 3 | export function toKebab(s: string): string; 4 | export function capitalized(s: string): string; 5 | -------------------------------------------------------------------------------- /types/general/transforms.d.ts: -------------------------------------------------------------------------------- 1 | export function parseTransform(transform: string): { 2 | transform: string; 3 | parameters: number[]; 4 | }[]; 5 | export function transformStringToMatrix(string: any): any; 6 | -------------------------------------------------------------------------------- /types/general/viewBox.d.ts: -------------------------------------------------------------------------------- 1 | export function setViewBox(element: any, ...args: any[]): any; 2 | export function getViewBox(element: any): any; 3 | export function convertToViewBox(svg: any, x: any, y: any): any[]; 4 | export function foldToViewBox({ vertices_coords }: { 5 | vertices_coords: any; 6 | }): string; 7 | -------------------------------------------------------------------------------- /types/spec/classes_attributes.d.ts: -------------------------------------------------------------------------------- 1 | export default classes_attributes; 2 | declare namespace classes_attributes { 3 | let presentation: string[]; 4 | let animation: string[]; 5 | let effects: string[]; 6 | let text: string[]; 7 | let gradient: string[]; 8 | } 9 | -------------------------------------------------------------------------------- /types/spec/classes_nodes.d.ts: -------------------------------------------------------------------------------- 1 | export default classes_nodes; 2 | declare namespace classes_nodes { 3 | let svg: string[]; 4 | let defs: string[]; 5 | let header: string[]; 6 | let cdata: string[]; 7 | let group: string[]; 8 | let visible: string[]; 9 | let text: string[]; 10 | let invisible: string[]; 11 | let patterns: string[]; 12 | let childrenOfText: string[]; 13 | let gradients: string[]; 14 | let filter: string[]; 15 | } 16 | -------------------------------------------------------------------------------- /types/spec/namespace.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: "http://www.w3.org/2000/svg"; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /types/spec/nodes.d.ts: -------------------------------------------------------------------------------- 1 | export const nodeNames: string[]; 2 | export default classes_nodes; 3 | import classes_nodes from "./classes_nodes.js"; 4 | -------------------------------------------------------------------------------- /types/spec/nodes_attributes.d.ts: -------------------------------------------------------------------------------- 1 | export default nodes_attributes; 2 | declare namespace nodes_attributes { 3 | let svg: string[]; 4 | let line: string[]; 5 | let rect: string[]; 6 | let circle: string[]; 7 | let ellipse: string[]; 8 | let polygon: string[]; 9 | let polyline: string[]; 10 | let path: string[]; 11 | let text: string[]; 12 | let mask: string[]; 13 | let symbol: string[]; 14 | let clipPath: string[]; 15 | let marker: string[]; 16 | let linearGradient: string[]; 17 | let radialGradient: string[]; 18 | let stop: string[]; 19 | let pattern: string[]; 20 | } 21 | -------------------------------------------------------------------------------- /types/spec/nodes_children.d.ts: -------------------------------------------------------------------------------- 1 | export default nodes_children; 2 | declare namespace nodes_children { 3 | export let svg: string[]; 4 | export { headerStuff as defs }; 5 | export let filter: string[]; 6 | export { drawingShapes as g }; 7 | export let text: string[]; 8 | export { drawingShapes as marker }; 9 | export { drawingShapes as symbol }; 10 | export { drawingShapes as clipPath }; 11 | export { drawingShapes as mask }; 12 | export let linearGradient: string[]; 13 | export let radialGradient: string[]; 14 | } 15 | declare const headerStuff: string[]; 16 | declare const drawingShapes: string[]; 17 | --------------------------------------------------------------------------------