From df5f6910846c4dbd9d6824fc5465efc2aab534c4 Mon Sep 17 00:00:00 2001 From: Maarten Smeyers Date: Mon, 2 Apr 2018 19:47:07 +0200 Subject: [PATCH] Cleanup of old files --- reo-online-editor/boss.html | 21 - reo-online-editor/boss.js | 58 -- reo-online-editor/channel.html | 69 -- reo-online-editor/channel.js | 495 ---------- .../fabric.js-1.7.3/dist/fabric.min.js | 9 - reo-online-editor/group.html | 45 - reo-online-editor/group.js | 116 --- reo-online-editor/playground/CAVEATS | 19 - reo-online-editor/playground/animation.js | 567 ----------- reo-online-editor/playground/canvas.js | 223 ----- reo-online-editor/playground/connector.js | 818 ---------------- reo-online-editor/playground/doc/animation.md | 67 -- reo-online-editor/playground/doc/connector.md | 109 --- .../playground/doc/references.md | 6 - reo-online-editor/playground/interface.js | 499 ---------- .../playground/playground-data.js | 920 ------------------ reo-online-editor/playground/playground.css | 131 --- reo-online-editor/playground/playground.html | 66 -- reo-online-editor/rect.html | 45 - reo-online-editor/rect.js | 198 ---- reo-online-editor/transform.html | 70 -- reo-online-editor/transform.js | 479 --------- 22 files changed, 5030 deletions(-) delete mode 100644 reo-online-editor/boss.html delete mode 100644 reo-online-editor/boss.js delete mode 100644 reo-online-editor/channel.html delete mode 100644 reo-online-editor/channel.js delete mode 100644 reo-online-editor/fabric.js-1.7.3/dist/fabric.min.js delete mode 100644 reo-online-editor/group.html delete mode 100644 reo-online-editor/group.js delete mode 100644 reo-online-editor/playground/CAVEATS delete mode 100644 reo-online-editor/playground/animation.js delete mode 100644 reo-online-editor/playground/canvas.js delete mode 100644 reo-online-editor/playground/connector.js delete mode 100644 reo-online-editor/playground/doc/animation.md delete mode 100644 reo-online-editor/playground/doc/connector.md delete mode 100644 reo-online-editor/playground/doc/references.md delete mode 100644 reo-online-editor/playground/interface.js delete mode 100644 reo-online-editor/playground/playground-data.js delete mode 100644 reo-online-editor/playground/playground.css delete mode 100644 reo-online-editor/playground/playground.html delete mode 100644 reo-online-editor/rect.html delete mode 100644 reo-online-editor/rect.js delete mode 100644 reo-online-editor/transform.html delete mode 100644 reo-online-editor/transform.js diff --git a/reo-online-editor/boss.html b/reo-online-editor/boss.html deleted file mode 100644 index edb0a818..00000000 --- a/reo-online-editor/boss.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Boss test - - - -
-
-
- -
-
- - - - \ No newline at end of file diff --git a/reo-online-editor/boss.js b/reo-online-editor/boss.js deleted file mode 100644 index 33ee67f3..00000000 --- a/reo-online-editor/boss.js +++ /dev/null @@ -1,58 +0,0 @@ -var canvas = new fabric.Canvas('c'); -var boss = new fabric.Line([0, 0, 100, 0], { - stroke: 'red', - strokeWidth: 5, - originX: 'center', - originY: 'center' - }); -var minion1 = new fabric.Triangle( - { width: 10, height: 10, fill: 'blue' }); - -canvas.add(boss, minion1); - -boss.on('moving', updateMinions); -boss.on('rotating', updateMinions); -boss.on('scaling', updateMinions); - -var multiply = fabric.util.multiplyTransformMatrices; -var invert = fabric.util.invertTransform; - -function updateMinions() { - var minions = canvas.getObjects().filter(o => o !== boss); - minions.forEach(o => { - if (!o.relationship) { - return; - } - var relationship = o.relationship; - var newTransform = multiply( - boss.calcTransformMatrix(), - relationship - ); - opt = fabric.util.qrDecompose(newTransform); - o.set({ - flipX: false, - flipY: false, - }); - o.setPositionByOrigin( - { x: opt.translateX, y: opt.translateY }, - 'center', - 'center' - ); - o.set(opt); - o.setCoords(); - }); -} - -document.getElementById('bind').onclick = function() { - var minions = canvas.getObjects().filter(o => o !== boss); - var bossTransform = boss.calcTransformMatrix(); - var invertedBossTransform = invert(bossTransform); - minions.forEach(o => { - var desiredTransform = multiply( - invertedBossTransform, - o.calcTransformMatrix() - ); - // save the desired relation here. - o.relationship = desiredTransform; - }); -} \ No newline at end of file diff --git a/reo-online-editor/channel.html b/reo-online-editor/channel.html deleted file mode 100644 index 87c3419e..00000000 --- a/reo-online-editor/channel.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - Channel test - - - -
-
- - -
-
- - - - - -
SelectNew component
- -
-
-

Channels

- - - - - - - - - - - - - - - - - -
- Sync
- Sync -
- Lossy sync
- Lossy sync -
- Sync drain
- Sync drain -
- Sync spout
- Sync spout -
- FIFO1
- FIFO1 -
-
-
- Please use a browser that supports canvas. -
-
- - - - \ No newline at end of file diff --git a/reo-online-editor/channel.js b/reo-online-editor/channel.js deleted file mode 100644 index ea8c96b7..00000000 --- a/reo-online-editor/channel.js +++ /dev/null @@ -1,495 +0,0 @@ -(function() { - - var canvas = this.__canvas = new fabric.Canvas('c', { selection: false }); - - fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'; - - fabric.Object.prototype.objectCaching = false; - var active, isDown, origX, origY, origLeft, origTop; - var mode = 'select'; - var id = '0'; - var nodes = []; - - // drawing parameters - - nodeFillColourSource = '#fff'; - nodeFillColourDrain = '#fff'; - nodeFillColourMixed = '#000'; - nodeFactor = 4; - - lineFillColour = '#000'; - lineStrokeColour = '#000'; - lineStrokeWidth = 1; - - arrowFactor = 8; - arrowOffsetOut = lineStrokeWidth * nodeFactor + 4; - arrowOffsetIn = arrowOffsetOut + arrowFactor; - - fifoHeight = 30; - fifoWidth = 10; - fifoFillColour = '#fff'; - - buttonBorderOff = '2px solid white'; - buttonBorderOn = '2px solid black'; - - document.getElementById("select").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'select'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': true}); - } - }); - }; - - document.getElementById("component").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'component'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("sync").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'sync'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("lossysync").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'lossysync'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("syncdrain").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'syncdrain'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("syncspout").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'syncspout'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("fifo1").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'fifo1'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("downloadsvg").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.svg"; - a.href = 'data:image/svg+xml;base64,' + window.btoa(canvas.toSVG()); - a.click(); - }; - - document.getElementById("downloadpng").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.png"; - a.href = canvas.toDataURL('image/png'); - a.click(); - }; - - // generate a new object ID - // ID will only contain letters, e.g. z is followed by aa - function generateId() { - id = ((parseInt(id, 36)+1).toString(36)).replace(/[0-9]/g,'a'); - return id; - } - - function createNode(left, top) { - var node = new fabric.Circle({ - left: left, - top: top, - strokeWidth: lineStrokeWidth, - radius: nodeFactor * lineStrokeWidth, - stroke: lineStrokeColour, - hasControls: false, - class: 'node', - id: generateId() - }); - - // these are the channels that are connected to this node - node.channels = []; - - var label = new fabric.IText(node.id, { - left: left + 20, - top: top - 20, - fontSize: 20, - object: node, - class: 'label', - hasControls: false - //visible: false - }); - - node.set({'label': label, 'labelOffsetX': 20, 'labelOffsetY': -20}); - - label.on('editing:exited', function(e) { - label.object.set({id: label.text}); - }); - - nodes.push(node); - - return node; - - } //createNode - - function createAnchor(left, top) { - var anchor = new fabric.Circle({ - left: left, - top: top, - strokeWidth: lineStrokeWidth, - radius: nodeFactor * lineStrokeWidth, - stroke: lineStrokeColour, - hasControls: false, - class: 'anchor', - opacity: 0 - }); - return anchor; - - } //createAnchor - - function createChannel(x1, y1, x2, y2) { - // create a channel... - var channel = {}; - channel.components = []; - - // ...two nodes... - channel.node1 = createNode(x1,y1); - channel.node2 = createNode(x2,y2); - - // ...and two anchors - // TODO - channel.anchor1 = createAnchor(133,100); - channel.anchor2 = createAnchor(167,100); - - // link the channel to the nodes - channel.node1.channels.push(channel); - channel.node2.channels.push(channel); - - return channel; - } //createChannel - - function createSync(x1, y1, x2, y2) { - // create a channel... - var sync = createChannel(x1, y1, x2, y2); - - sync.name = 'lossysync'; - sync.end1 = 'source'; - sync.end2 = 'sink'; - - // ...a line... - var line = new fabric.Line([x1, y1, x2, y2], { - fill: lineFillColour, - stroke: lineStrokeColour, - strokeWidth: lineStrokeWidth, - hasBorders: false, - hasControls: false, - selectable: false, - hoverCursor: 'default', - }); - sync.components.push(line); - - // ...and an arrowhead - var a = new fabric.Triangle({ - left: x2, - top: y2, - width: arrowFactor * lineStrokeWidth, - height: arrowFactor * lineStrokeWidth, - baseAngle: 90, - rotate: true, - fill: lineFillColour, - hasBorders: false, - hasControls: false, - selectable: false, - hoverCursor: 'default', - offset: arrowOffsetOut, - reference: 'node2' - }); - sync.components.push(a); - - drawChannel(sync); - return sync; - } //createSync - - function createLossySync(x1, y1, x2, y2) { - // create a channel... - var lossysync = createChannel(x1, y1, x2, y2); - - lossysync.name = 'lossysync'; - lossysync.end1 = 'source'; - lossysync.end2 = 'sink'; - - // ...a line... - var line = new fabric.Line([x1, y1, x2, y2], { - fill: lineFillColour, - stroke: lineStrokeColour, - strokeDashArray: [8,8], - strokeWidth: lineStrokeWidth, - hasBorders: false, - hasControls: false, - selectable: false, - hoverCursor: 'default', - }); - lossysync.components.push(line); - - // ...and an arrowhead - var a = new fabric.Triangle({ - left: x2, - top: y2, - width: arrowFactor * lineStrokeWidth, - height: arrowFactor * lineStrokeWidth, - baseAngle: 90, - rotate: true, - fill: lineFillColour, - hasBorders: false, - hasControls: false, - selectable: false, - hoverCursor: 'default', - offset: arrowOffsetOut, - reference: 'node2' - }); - lossysync.components.push(a); - - drawChannel(lossysync); - return lossysync; - } //createLossySync - - function drawChannel(channel) { - for (i = 0; i < channel.components.length; i++) - canvas.add(channel.components[i]); - canvas.add(channel.node1, channel.node2, channel.node1.label, channel.node2.label, channel.anchor1, channel.anchor2); - updateChannel(channel); - } - - function calculateAngle(channel, baseAngle) { - var angle = 0; - var x = (channel.node2.get('left') - channel.node1.get('left')); - var y = (channel.node2.get('top') - channel.node1.get('top')); - - if (x === 0) { - angle = (y === 0) ? 0 : (y > 0) ? Math.PI / 2 : Math.PI * 3 / 2; - } else if (y === 0) { - angle = (x > 0) ? 0 : Math.PI; - } else { - angle = (x < 0) ? Math.atan(y / x) + Math.PI : (y < 0) ? Math.atan(y / x) + (2 * Math.PI) : Math.atan(y / x); - } - - return ((angle * 180 / Math.PI) + baseAngle) % 360; - } //calculateAngle - - function updateChannel (channel) { - var x1 = channel.node1.get('left'); - var y1 = channel.node1.get('top'); - var x2 = channel.node2.get('left'); - var y2 = channel.node2.get('top'); - - var length = Math.sqrt(Math.pow(x2 - x1,2) + Math.pow(y2 - y1,2)); - var x = Math.atan(Math.abs(y1 - y2)/Math.abs(x1 - x2)); - - for(k = 0; k < channel.components.length; k++) { - var c = channel.components[k]; - switch (c.type) { - case "line": - c.set({'x1': x1}); - c.set({'y1': y1}); - c.set({'x2': x2}); - c.set({'y2': y2}); - break; - case "triangle": - // place the component at the proper coordinates - switch (c.reference) { - case "node1": - if (x2 > x1) - c.set({'left': x1 + c.offset * Math.cos(x)}); - else - c.set({'left': x1 - c.offset * Math.cos(x)}); - if (y2 > y1) - c.set({'top': y1 + c.offset * Math.sin(x)}); - else - c.set({'top': y1 - c.offset * Math.sin(x)}); - break; - case "node2": - if (x2 > x1) - c.set({'left': x2 - c.offset * Math.cos(x)}); - else - c.set({'left': x2 + c.offset * Math.cos(x)}); - if (y2 > y1) - c.set({'top': y2 - c.offset * Math.sin(x)}); - else - c.set({'top': y2 + c.offset * Math.sin(x)}); - break; - } - - // adjust the rotation if necessary - if (c.rotate == true) - c.setAngle(calculateAngle(channel, c.baseAngle)); - else - c.setAngle(c.baseAngle); - break; - } - c.setCoords(); - } - canvas.renderAll(); - } - - canvas.on('object:moving', function(e) { - e.target.setCoords(); - }); //object:moving - - canvas.on('mouse:over', function(e) { - if (e.target && e.target.class == "anchor") - { - e.target.set('opacity', '100'); - canvas.renderAll(); - } - }); //mouse:over - - canvas.on('mouse:out', function(e) { - if (e.target && e.target.class == "anchor") - { - e.target.set('opacity', '0'); - canvas.renderAll(); - } - }); //mouse:out - - canvas.on('mouse:down', function(e) { - isDown = true; - var pointer = canvas.getPointer(e.e); - origX = pointer.x; - origY = pointer.y; - var p = canvas.getActiveObject(); - if (p && mode != 'select') { - origLeft = p.left; - origTop = p.top; - return; - } - if (mode == 'select') { - //console.log('Mode is select'); - } - if (mode == 'sync') { - canvas.deactivateAll(); - var channel = createSync(pointer.x,pointer.y,pointer.x,pointer.y); - canvas.setActiveObject(channel.node2); - } - if (mode == 'lossysync') { - canvas.deactivateAll(); - var channel = createLossySync(pointer.x,pointer.y,pointer.x,pointer.y); - canvas.setActiveObject(channel.node2); - } - }); //mouse:down - - canvas.on('mouse:move', function(e){ - if (!isDown) - return; - var p = canvas.getActiveObject(); - if (!p) - return; - var pointer = canvas.getPointer(e.e); - if (p.class == 'node') { - p.set({'left': pointer.x, 'top': pointer.y}); - p.setCoords(); - canvas.forEachObject(function(obj) { - if (obj !== p && p.intersectsWithObject(obj)) { - if (obj.class === 'node') { - if (Math.abs(p.left-obj.left) < 10 && Math.abs(p.top-obj.top) < 10) { - p.set({'left': obj.left, 'top': obj.top}); - p.setCoords(); - } - } - } - }); - - for (i = 0; i < p.channels.length; i++) - updateChannel(p.channels[i]); - - p.label.set({left: p.left + p.labelOffsetX}); - p.label.set({top: p.top + p.labelOffsetY}); - p.label.setCoords(); - } - canvas.renderAll(); - }); //mouse:move - - canvas.on('mouse:up', function(e){ - isDown = false; - var p = canvas.getActiveObject(); - if (p) { - p.setCoords(); - if (p.class == 'node') { - p.label.setCoords(); - p.set({labelOffsetX: p.label.left - p.left, labelOffsetY: p.label.top - p.top}); - var bin = []; - for (i = nodes.length - 1; i >= 0; i--) { - if (nodes[i].id == p.id) - continue; - var obj = nodes[i]; - if (p.intersectsWithObject(obj)) { - if(Math.abs(p.left-obj.left) < 10 && Math.abs(p.top-obj.top) < 10) { - for (j = 0; j < p.channels.length; j++) { - if (p.channels[j].node1.id == p.id) { - p.channels[j].node1 = obj; - } - else { - if (p.channels[j].node2.id == p.id) - p.channels[j].node2 = obj; - else - console.log("Error merging nodes"); - } - obj.channels.push(p.channels[j]); - } - canvas.remove(p.label, p); - obj.bringToFront(); - } - } - } - canvas.renderAll(); - canvas.calcOffset(); - } - if (p.class == 'label') { - p.setCoords(); - p.object.set({'labelOffsetX': p.left - p.object.left, 'labelOffsetY': p.top - p.object.top}); - } - else - canvas.deactivateAll(); - canvas.renderAll(); - } - }); //mouse:up - - id = '0'; - document.getElementById("select").click(); - createSync(100,100,200,100); - createLossySync(300,100,400,100); -})(); diff --git a/reo-online-editor/fabric.js-1.7.3/dist/fabric.min.js b/reo-online-editor/fabric.js-1.7.3/dist/fabric.min.js deleted file mode 100644 index 81147c90..00000000 --- a/reo-online-editor/fabric.js-1.7.3/dist/fabric.min.js +++ /dev/null @@ -1,9 +0,0 @@ -var fabric=fabric||{version:"1.7.3"};"undefined"!=typeof exports&&(exports.fabric=fabric),"undefined"!=typeof document&&"undefined"!=typeof window?(fabric.document=document,fabric.window=window,window.fabric=fabric):(fabric.document=require("jsdom").jsdom(decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E")),fabric.document.createWindow?fabric.window=fabric.document.createWindow():fabric.window=fabric.document.parentWindow),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","id"],fabric.DPI=96,fabric.reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",fabric.fontPaths={},fabric.charWidthsCache={},fabric.devicePixelRatio=fabric.window.devicePixelRatio||fabric.window.webkitDevicePixelRatio||fabric.window.mozDevicePixelRatio||1,function(){function t(t,e){if(this.__eventListeners[t]){var i=this.__eventListeners[t];e?i[i.indexOf(e)]=!1:fabric.util.array.fill(i,!1)}}function e(t,e){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var i in t)this.on(i,t[i]);else this.__eventListeners[t]||(this.__eventListeners[t]=[]),this.__eventListeners[t].push(e);return this}function i(e,i){if(this.__eventListeners){if(0===arguments.length)for(e in this.__eventListeners)t.call(this,e);else if(1===arguments.length&&"object"==typeof arguments[0])for(var r in e)t.call(this,r,e[r]);else t.call(this,e,i);return this}}function r(t,e){if(this.__eventListeners){var i=this.__eventListeners[t];if(i){for(var r=0,n=i.length;r-1},complexity:function(){return this.getObjects().reduce(function(t,e){return t+=e.complexity?e.complexity():0},0)}},fabric.CommonMethods={_setOptions:function(t){for(var e in t)this.set(e,t[e])},_initGradient:function(t,e){!t||!t.colorStops||t instanceof fabric.Gradient||this.set(e,new fabric.Gradient(t))},_initPattern:function(t,e,i){!t||!t.source||t instanceof fabric.Pattern?i&&i():this.set(e,new fabric.Pattern(t,i))},_initClipping:function(t){if(t.clipTo&&"string"==typeof t.clipTo){var e=fabric.util.getFunctionBody(t.clipTo);"undefined"!=typeof e&&(this.clipTo=new Function("ctx",e))}},_setObject:function(t){for(var e in t)this._set(e,t[e])},set:function(t,e){return"object"==typeof t?this._setObject(t):"function"==typeof e&&"clipTo"!==t?this._set(t,e(this.get(t))):this._set(t,e),this},_set:function(t,e){this[t]=e},toggle:function(t){var e=this.get(t);return"boolean"==typeof e&&this.set(t,!e),this},get:function(t){return this[t]}},function(t){var e=Math.sqrt,i=Math.atan2,r=Math.pow,n=Math.abs,s=Math.PI/180;fabric.util={removeFromArray:function(t,e){var i=t.indexOf(e);return i!==-1&&t.splice(i,1),t},getRandomInt:function(t,e){return Math.floor(Math.random()*(e-t+1))+t},degreesToRadians:function(t){return t*s},radiansToDegrees:function(t){return t/s},rotatePoint:function(t,e,i){t.subtractEquals(e);var r=fabric.util.rotateVector(t,i);return new fabric.Point(r.x,r.y).addEquals(e)},rotateVector:function(t,e){var i=Math.sin(e),r=Math.cos(e),n=t.x*r-t.y*i,s=t.x*i+t.y*r;return{x:n,y:s}},transformPoint:function(t,e,i){return i?new fabric.Point(e[0]*t.x+e[2]*t.y,e[1]*t.x+e[3]*t.y):new fabric.Point(e[0]*t.x+e[2]*t.y+e[4],e[1]*t.x+e[3]*t.y+e[5])},makeBoundingBoxFromPoints:function(t){var e=[t[0].x,t[1].x,t[2].x,t[3].x],i=fabric.util.array.min(e),r=fabric.util.array.max(e),n=Math.abs(i-r),s=[t[0].y,t[1].y,t[2].y,t[3].y],o=fabric.util.array.min(s),a=fabric.util.array.max(s),h=Math.abs(o-a);return{left:i,top:o,width:n,height:h}},invertTransform:function(t){var e=1/(t[0]*t[3]-t[1]*t[2]),i=[e*t[3],-e*t[1],-e*t[2],e*t[0]],r=fabric.util.transformPoint({x:t[4],y:t[5]},i,!0);return i[4]=-r.x,i[5]=-r.y,i},toFixed:function(t,e){return parseFloat(Number(t).toFixed(e))},parseUnit:function(t,e){var i=/\D{0,2}$/.exec(t),r=parseFloat(t);switch(e||(e=fabric.Text.DEFAULT_SVG_FONT_SIZE),i[0]){case"mm":return r*fabric.DPI/25.4;case"cm":return r*fabric.DPI/2.54;case"in":return r*fabric.DPI;case"pt":return r*fabric.DPI/72;case"pc":return r*fabric.DPI/72*12;case"em":return r*e;default:return r}},falseFunction:function(){return!1},getKlass:function(t,e){return t=fabric.util.string.camelize(t.charAt(0).toUpperCase()+t.slice(1)),fabric.util.resolveNamespace(e)[t]},resolveNamespace:function(e){if(!e)return fabric;var i,r=e.split("."),n=r.length,s=t||fabric.window;for(i=0;ir;)r+=a[d++%f],r>l&&(r=l),t[g?"lineTo":"moveTo"](r,0),g=!g;t.restore()},createCanvasElement:function(t){return t||(t=fabric.document.createElement("canvas")),t.getContext||"undefined"==typeof G_vmlCanvasManager||G_vmlCanvasManager.initElement(t),t},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(t){var e,i,r,n,s,o=t.prototype;for(e=o.stateProperties.length;e--;)i=o.stateProperties[e],r=i.charAt(0).toUpperCase()+i.slice(1),n="set"+r,s="get"+r,o[s]||(o[s]=function(t){return new Function('return this.get("'+t+'")')}(i)),o[n]||(o[n]=function(t){return new Function("value",'return this.set("'+t+'", value)')}(i))},clipContext:function(t,e){e.save(),e.beginPath(),t.clipTo(e),e.clip()},multiplyTransformMatrices:function(t,e,i){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],i?0:t[0]*e[4]+t[2]*e[5]+t[4],i?0:t[1]*e[4]+t[3]*e[5]+t[5]]},qrDecompose:function(t){var n=i(t[1],t[0]),o=r(t[0],2)+r(t[1],2),a=e(o),h=(t[0]*t[3]-t[2]*t[1])/a,c=i(t[0]*t[2]+t[1]*t[3],o);return{angle:n/s,scaleX:a,scaleY:h,skewX:c/s,skewY:0,translateX:t[4],translateY:t[5]}},customTransformMatrix:function(t,e,i){var r=[1,0,n(Math.tan(i*s)),1],o=[n(t),0,0,n(e)];return fabric.util.multiplyTransformMatrices(o,r,!0)},resetObjectTransform:function(t){t.scaleX=1,t.scaleY=1,t.skewX=0,t.skewY=0,t.flipX=!1,t.flipY=!1,t.setAngle(0)},getFunctionBody:function(t){return(String(t).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(t,e,i,r){r>0&&(e>r?e-=r:e=0,i>r?i-=r:i=0);var n,s,o=!0,a=t.getImageData(e,i,2*r||1,2*r||1),h=a.data.length;for(n=3;n0?D-=2*f:1===c&&D<0&&(D+=2*f);for(var I=Math.ceil(Math.abs(D/f*2)),E=[],L=D/I,F=8/3*Math.sin(L/4)*Math.sin(L/4)/Math.sin(L/2),R=A+L,B=0;B=n?s-n:2*Math.PI-(n-s)}function r(t,e,i,r,n,s,h,c){var l=a.call(arguments);if(o[l])return o[l];var u,f,d,g,p,v,b,m,y=Math.sqrt,_=Math.min,x=Math.max,C=Math.abs,S=[],w=[[],[]];f=6*t-12*i+6*n,u=-3*t+9*i-9*n+3*h,d=3*i-3*t;for(var O=0;O<2;++O)if(O>0&&(f=6*e-12*r+6*s,u=-3*e+9*r-9*s+3*c,d=3*r-3*e),C(u)<1e-12){if(C(f)<1e-12)continue;g=-d/f,0=e})}function i(t,e){return n(t,e,function(t,e){return t>>0;if(0===i)return-1;var r=0;if(arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:0!==r&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r)))),r>=i)return-1;for(var n=r>=0?r:Math.max(i-Math.abs(r),0);n>>0;i>>0;r>>0;i>>0;i>>0;n>>0,r=0;if(arguments.length>1)e=arguments[1];else for(;;){if(r in this){e=this[r++];break}if(++r>=i)throw new TypeError}for(;r/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:t,capitalize:e,escapeXml:i}}(),function(){var t=Array.prototype.slice,e=Function.prototype.apply,i=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var n,s=this,o=t.call(arguments,1);return n=o.length?function(){return e.call(s,this instanceof i?this:r,o.concat(t.call(arguments)))}:function(){return e.call(s,this instanceof i?this:r,arguments)},i.prototype=this.prototype,n.prototype=new i,n})}(),function(){function t(){}function e(t){var e=this.constructor.superclass.prototype[t];return arguments.length>1?e.apply(this,r.call(arguments,1)):e.call(this)}function i(){function i(){this.initialize.apply(this,arguments)}var s=null,a=r.call(arguments,0);"function"==typeof a[0]&&(s=a.shift()),i.superclass=s,i.subclasses=[],s&&(t.prototype=s.prototype,i.prototype=new t,s.subclasses.push(i));for(var h=0,c=a.length;h-1?t.prototype[r]=function(t){return function(){var r=this.constructor.superclass;this.constructor.superclass=i;var n=e[t].apply(this,arguments);if(this.constructor.superclass=r,"initialize"!==t)return n}}(r):t.prototype[r]=e[r],s&&(e.toString!==Object.prototype.toString&&(t.prototype.toString=e.toString),e.valueOf!==Object.prototype.valueOf&&(t.prototype.valueOf=e.valueOf))};fabric.util.createClass=i}(),function(){function t(t){var e,i,r=Array.prototype.slice.call(arguments,1),n=r.length;for(i=0;i-1?s(t,e.match(/opacity:\s*(\d?\.?\d*)/)[1]):t;for(var r in e)if("opacity"===r)s(t,e[r]);else{var n="float"===r||"cssFloat"===r?"undefined"==typeof i.styleFloat?"cssFloat":"styleFloat":r;i[n]=e[r]}return t}var e=fabric.document.createElement("div"),i="string"==typeof e.style.opacity,r="string"==typeof e.style.filter,n=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(t){return t};i?s=function(t,e){return t.style.opacity=e,t}:r&&(s=function(t,e){var i=t.style;return t.currentStyle&&!t.currentStyle.hasLayout&&(i.zoom=1),n.test(i.filter)?(e=e>=.9999?"":"alpha(opacity="+100*e+")",i.filter=i.filter.replace(n,e)):i.filter+=" alpha(opacity="+100*e+")",t}),fabric.util.setStyle=t}(),function(){function t(t){return"string"==typeof t?fabric.document.getElementById(t):t}function e(t,e){var i=fabric.document.createElement(t);for(var r in e)"class"===r?i.className=e[r]:"for"===r?i.htmlFor=e[r]:i.setAttribute(r,e[r]);return i}function i(t,e){t&&(" "+t.className+" ").indexOf(" "+e+" ")===-1&&(t.className+=(t.className?" ":"")+e)}function r(t,i,r){return"string"==typeof i&&(i=e(i,r)),t.parentNode&&t.parentNode.replaceChild(i,t),i.appendChild(t),i}function n(t){for(var e=0,i=0,r=fabric.document.documentElement,n=fabric.document.body||{scrollLeft:0,scrollTop:0};t&&(t.parentNode||t.host)&&(t=t.parentNode||t.host,t===fabric.document?(e=n.scrollLeft||r.scrollLeft||0,i=n.scrollTop||r.scrollTop||0):(e+=t.scrollLeft||0,i+=t.scrollTop||0),1!==t.nodeType||"fixed"!==fabric.util.getElementStyle(t,"position")););return{left:e,top:i}}function s(t){var e,i,r=t&&t.ownerDocument,s={left:0,top:0},o={left:0,top:0},a={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!r)return o;for(var h in a)o[a[h]]+=parseInt(c(t,h),10)||0;return e=r.documentElement,"undefined"!=typeof t.getBoundingClientRect&&(s=t.getBoundingClientRect()),i=n(t),{left:s.left+i.left-(e.clientLeft||0)+o.left,top:s.top+i.top-(e.clientTop||0)+o.top}}var o,a=Array.prototype.slice,h=function(t){return a.call(t,0)};try{o=h(fabric.document.childNodes)instanceof Array}catch(t){}o||(h=function(t){for(var e=new Array(t.length),i=t.length;i--;)e[i]=t[i];return e});var c;c=fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?function(t,e){var i=fabric.document.defaultView.getComputedStyle(t,null);return i?i[e]:void 0}:function(t,e){var i=t.style[e];return!i&&t.currentStyle&&(i=t.currentStyle[e]),i},function(){function t(t){return"undefined"!=typeof t.onselectstart&&(t.onselectstart=fabric.util.falseFunction),r?t.style[r]="none":"string"==typeof t.unselectable&&(t.unselectable="on"),t}function e(t){return"undefined"!=typeof t.onselectstart&&(t.onselectstart=null),r?t.style[r]="":"string"==typeof t.unselectable&&(t.unselectable=""),t}var i=fabric.document.documentElement.style,r="userSelect"in i?"userSelect":"MozUserSelect"in i?"MozUserSelect":"WebkitUserSelect"in i?"WebkitUserSelect":"KhtmlUserSelect"in i?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=t,fabric.util.makeElementSelectable=e}(),function(){function t(t,e){var i=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),n=!0;r.onload=r.onreadystatechange=function(t){if(n){if("string"==typeof this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)return;n=!1,e(t||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=t,i.appendChild(r)}fabric.util.getScript=t}(),fabric.util.getById=t,fabric.util.toArray=h,fabric.util.makeElement=e,fabric.util.addClass=i,fabric.util.wrapElement=r,fabric.util.getScrollLeftTop=n,fabric.util.getElementOffset=s,fabric.util.getElementStyle=c}(),function(){function t(t,e){return t+(/\?/.test(t)?"&":"?")+e}function e(){}function i(i,n){n||(n={});var s=n.method?n.method.toUpperCase():"GET",o=n.onComplete||function(){},a=r(),h=n.body||n.parameters;return a.onreadystatechange=function(){4===a.readyState&&(o(a),a.onreadystatechange=e)},"GET"===s&&(h=null,"string"==typeof n.parameters&&(i=t(i,n.parameters))),a.open(s,i,!0),"POST"!==s&&"PUT"!==s||a.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),a.send(h),a}var r=function(){for(var t=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}],e=t.length;e--;)try{var i=t[e]();if(i)return t[e]}catch(t){}}();fabric.util.request=i}(),fabric.log=function(){},fabric.warn=function(){},"undefined"!=typeof console&&["log","warn"].forEach(function(t){"undefined"!=typeof console[t]&&"function"==typeof console[t].apply&&(fabric[t]=function(){return console[t].apply(console,arguments)})}),function(){function t(t){e(function(i){t||(t={});var r,n=i||+new Date,s=t.duration||500,o=n+s,a=t.onChange||function(){},h=t.abort||function(){return!1},c=t.easing||function(t,e,i,r){return-i*Math.cos(t/r*(Math.PI/2))+i+e},l="startValue"in t?t.startValue:0,u="endValue"in t?t.endValue:100,f=t.byValue||u-l;t.onStart&&t.onStart(),function i(u){r=u||+new Date;var d=r>o?s:r-n;return h()?void(t.onComplete&&t.onComplete()):(a(c(d,l,f,s)),r>o?void(t.onComplete&&t.onComplete()):void e(i))}(n)})}function e(){return i.apply(fabric.window,arguments)}var i=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(t){fabric.window.setTimeout(t,1e3/60)};fabric.util.animate=t,fabric.util.requestAnimFrame=e}(),function(){function t(t,e,i){var r="rgba("+parseInt(t[0]+i*(e[0]-t[0]),10)+","+parseInt(t[1]+i*(e[1]-t[1]),10)+","+parseInt(t[2]+i*(e[2]-t[2]),10);return r+=","+(t&&e?parseFloat(t[3]+i*(e[3]-t[3])):1),r+=")"}function e(e,i,r,n){var s=new fabric.Color(e).getSource(),o=new fabric.Color(i).getSource();n=n||{},fabric.util.animate(fabric.util.object.extend(n,{duration:r||500,startValue:s,endValue:o,byValue:o,easing:function(e,i,r,s){var o=n.colorEasing?n.colorEasing(e,s):1-Math.cos(e/s*(Math.PI/2));return t(i,r,o)}}))}fabric.util.animateColor=e}(),function(){function t(t,e,i,r){return ta?a:o),1===o&&1===a&&0===h&&0===c&&0===f&&0===d)return _;if((f||d)&&(x=" translate("+y(f)+" "+y(d)+") "),r=x+" matrix("+o+" 0 0 "+a+" "+h*o+" "+c*a+") ","svg"===t.nodeName){for(n=t.ownerDocument.createElement("g");t.firstChild;)n.appendChild(t.firstChild);t.appendChild(n)}else n=t,r=n.getAttribute("transform")+r;return n.setAttribute("transform",r),_}function g(t,e){for(;t&&(t=t.parentNode);)if(t.nodeName&&e.test(t.nodeName.replace("svg:",""))&&!t.getAttribute("instantiated_by_use"))return!0; -return!1}var p=t.fabric||(t.fabric={}),v=p.util.object.extend,b=p.util.object.clone,m=p.util.toFixed,y=p.util.parseUnit,_=p.util.multiplyTransformMatrices,x=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i,C=/^(symbol|image|marker|pattern|view|svg)$/i,S=/^(?:pattern|defs|symbol|metadata|clipPath|mask)$/i,w=/^(symbol|g|a|svg)$/i,O={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},T={stroke:"strokeOpacity",fill:"fillOpacity"};p.cssRules={},p.gradientDefs={},p.parseTransformAttribute=function(){function t(t,e){var i=Math.cos(e[0]),r=Math.sin(e[0]),n=0,s=0;3===e.length&&(n=e[1],s=e[2]),t[0]=i,t[1]=r,t[2]=-r,t[3]=i,t[4]=n-(i*n-r*s),t[5]=s-(r*n+i*s)}function e(t,e){var i=e[0],r=2===e.length?e[1]:e[0];t[0]=i,t[3]=r}function i(t,e,i){t[i]=Math.tan(p.util.degreesToRadians(e[0]))}function r(t,e){t[4]=e[0],2===e.length&&(t[5]=e[1])}var n=[1,0,0,1,0,0],s=p.reNum,o="(?:\\s+,?\\s*|,\\s*)",a="(?:(skewX)\\s*\\(\\s*("+s+")\\s*\\))",h="(?:(skewY)\\s*\\(\\s*("+s+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+s+")(?:"+o+"("+s+")"+o+"("+s+"))?\\s*\\))",l="(?:(scale)\\s*\\(\\s*("+s+")(?:"+o+"("+s+"))?\\s*\\))",u="(?:(translate)\\s*\\(\\s*("+s+")(?:"+o+"("+s+"))?\\s*\\))",f="(?:(matrix)\\s*\\(\\s*("+s+")"+o+"("+s+")"+o+"("+s+")"+o+"("+s+")"+o+"("+s+")"+o+"("+s+")\\s*\\))",d="(?:"+f+"|"+u+"|"+l+"|"+c+"|"+a+"|"+h+")",g="(?:"+d+"(?:"+o+"*"+d+")*)",v="^\\s*(?:"+g+"?)\\s*$",b=new RegExp(v),m=new RegExp(d,"g");return function(s){var o=n.concat(),a=[];if(!s||s&&!b.test(s))return o;s.replace(m,function(s){var h=new RegExp(d).exec(s).filter(function(t){return!!t}),c=h[1],l=h.slice(2).map(parseFloat);switch(c){case"translate":r(o,l);break;case"rotate":l[0]=p.util.degreesToRadians(l[0]),t(o,l);break;case"scale":e(o,l);break;case"skewX":i(o,l,2);break;case"skewY":i(o,l,1);break;case"matrix":o=l}a.push(o.concat()),o=n.concat()});for(var h=a[0];a.length>1;)a.shift(),h=p.util.multiplyTransformMatrices(h,a[0]);return h}}();var j=new RegExp("^\\s*("+p.reNum+"+)\\s*,?\\s*("+p.reNum+"+)\\s*,?\\s*("+p.reNum+"+)\\s*,?\\s*("+p.reNum+"+)\\s*$");p.parseSVGDocument=function(t,e,i){if(t){f(t);var r=p.Object.__uid++,n=d(t),s=p.util.toArray(t.getElementsByTagName("*"));if(n.svgUid=r,0===s.length&&p.isLikelyNode){s=t.selectNodes('//*[name(.)!="svg"]');for(var o=[],a=0,h=s.length;a/i,""))),r&&r.documentElement||e&&e(null),p.parseSVGDocument(r.documentElement,function(t,i){e&&e(t,i)},i)}t=t.replace(/^\n\s*/,"").trim(),new p.util.request(t,{method:"get",onComplete:r})},loadSVGFromString:function(t,e,i){t=t.trim();var r;if("undefined"!=typeof DOMParser){var n=new DOMParser;n&&n.parseFromString&&(r=n.parseFromString(t,"text/xml"))}else p.window.ActiveXObject&&(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(t.replace(//i,"")));p.parseSVGDocument(r.documentElement,function(t,i){e(t,i)},i)}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(t,e,i,r){this.elements=t,this.callback=e,this.options=i,this.reviver=r,this.svgUid=i&&i.svgUid||0},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var t=0,e=this.elements.length;tt.x&&this.y>t.y},gte:function(t){return this.x>=t.x&&this.y>=t.y},lerp:function(t,i){return"undefined"==typeof i&&(i=.5),i=Math.max(Math.min(1,i),0),new e(this.x+(t.x-this.x)*i,this.y+(t.y-this.y)*i)},distanceFrom:function(t){var e=this.x-t.x,i=this.y-t.y;return Math.sqrt(e*e+i*i)},midPointFrom:function(t){return this.lerp(t)},min:function(t){return new e(Math.min(this.x,t.x),Math.min(this.y,t.y))},max:function(t){return new e(Math.max(this.x,t.x),Math.max(this.y,t.y))},toString:function(){return this.x+","+this.y},setXY:function(t,e){return this.x=t,this.y=e,this},setX:function(t){return this.x=t,this},setY:function(t){return this.y=t,this},setFromPoint:function(t){return this.x=t.x,this.y=t.y,this},swap:function(t){var e=this.x,i=this.y;this.x=t.x,this.y=t.y,t.x=e,t.y=i},clone:function(){return new e(this.x,this.y)}}))}("undefined"!=typeof exports?exports:this),function(t){"use strict";function e(t){this.status=t,this.points=[]}var i=t.fabric||(t.fabric={});return i.Intersection?void i.warn("fabric.Intersection is already defined"):(i.Intersection=e,i.Intersection.prototype={constructor:e,appendPoint:function(t){return this.points.push(t),this},appendPoints:function(t){return this.points=this.points.concat(t),this}},i.Intersection.intersectLineLine=function(t,r,n,s){var o,a=(s.x-n.x)*(t.y-n.y)-(s.y-n.y)*(t.x-n.x),h=(r.x-t.x)*(t.y-n.y)-(r.y-t.y)*(t.x-n.x),c=(s.y-n.y)*(r.x-t.x)-(s.x-n.x)*(r.y-t.y);if(0!==c){var l=a/c,u=h/c;0<=l&&l<=1&&0<=u&&u<=1?(o=new e("Intersection"),o.appendPoint(new i.Point(t.x+l*(r.x-t.x),t.y+l*(r.y-t.y)))):o=new e}else o=new e(0===a||0===h?"Coincident":"Parallel");return o},i.Intersection.intersectLinePolygon=function(t,i,r){for(var n,s,o,a=new e,h=r.length,c=0;c0&&(a.status="Intersection"),a},i.Intersection.intersectPolygonPolygon=function(t,i){for(var r=new e,n=t.length,s=0;s0&&(r.status="Intersection"),r},void(i.Intersection.intersectPolygonRectangle=function(t,r,n){var s=r.min(n),o=r.max(n),a=new i.Point(o.x,s.y),h=new i.Point(s.x,o.y),c=e.intersectLinePolygon(s,a,t),l=e.intersectLinePolygon(a,o,t),u=e.intersectLinePolygon(o,h,t),f=e.intersectLinePolygon(h,s,t),d=new e;return d.appendPoints(c.points),d.appendPoints(l.points),d.appendPoints(u.points),d.appendPoints(f.points),d.points.length>0&&(d.status="Intersection"),d}))}("undefined"!=typeof exports?exports:this),function(t){"use strict";function e(t){t?this._tryParsingColor(t):this.setSource([0,0,0,1])}function i(t,e,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}var r=t.fabric||(t.fabric={});return r.Color?void r.warn("fabric.Color is already defined."):(r.Color=e,r.Color.prototype={_tryParsingColor:function(t){var i;t in e.colorNameMap&&(t=e.colorNameMap[t]),"transparent"===t&&(i=[255,255,255,0]),i||(i=e.sourceFromHex(t)),i||(i=e.sourceFromRgb(t)),i||(i=e.sourceFromHsl(t)),i||(i=[0,0,0,1]),i&&this.setSource(i)},_rgbToHsl:function(t,e,i){t/=255,e/=255,i/=255;var n,s,o,a=r.util.array.max([t,e,i]),h=r.util.array.min([t,e,i]);if(o=(a+h)/2,a===h)n=s=0;else{var c=a-h;switch(s=o>.5?c/(2-a-h):c/(a+h),a){case t:n=(e-i)/c+(e1?1:s,n){var o=n.split(/\s*;\s*/);""===o[o.length-1]&&o.pop();for(var a=o.length;a--;){var h=o[a].split(/\s*:\s*/),c=h[0].trim(),l=h[1].trim();"stop-color"===c?e=l:"stop-opacity"===c&&(r=l)}}return e||(e=t.getAttribute("stop-color")||"rgb(0,0,0)"),r||(r=t.getAttribute("stop-opacity")),e=new fabric.Color(e),i=e.getAlpha(),r=isNaN(parseFloat(r))?1:parseFloat(r),r*=i,{offset:s,color:e.toRgb(),opacity:r}}function e(t){return{x1:t.getAttribute("x1")||0,y1:t.getAttribute("y1")||0,x2:t.getAttribute("x2")||"100%",y2:t.getAttribute("y2")||0}}function i(t){return{x1:t.getAttribute("fx")||t.getAttribute("cx")||"50%",y1:t.getAttribute("fy")||t.getAttribute("cy")||"50%",r1:0,x2:t.getAttribute("cx")||"50%",y2:t.getAttribute("cy")||"50%",r2:t.getAttribute("r")||"50%"}}function r(t,e,i){var r,n=0,s=1,o="";for(var a in e)"Infinity"===e[a]?e[a]=1:"-Infinity"===e[a]&&(e[a]=0),r=parseFloat(e[a],10),s="string"==typeof e[a]&&/^\d+%$/.test(e[a])?.01:1,"x1"===a||"x2"===a||"r2"===a?(s*="objectBoundingBox"===i?t.width:1,n="objectBoundingBox"===i?t.left||0:0):"y1"!==a&&"y2"!==a||(s*="objectBoundingBox"===i?t.height:1,n="objectBoundingBox"===i?t.top||0:0),e[a]=r*s+n;if("ellipse"===t.type&&null!==e.r2&&"objectBoundingBox"===i&&t.rx!==t.ry){var h=t.ry/t.rx;o=" scale(1, "+h+")",e.y1&&(e.y1/=h),e.y2&&(e.y2/=h)}return o}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(t){t||(t={});var e={};this.id=fabric.Object.__uid++,this.type=t.type||"linear",e={x1:t.coords.x1||0,y1:t.coords.y1||0,x2:t.coords.x2||0,y2:t.coords.y2||0},"radial"===this.type&&(e.r1=t.coords.r1||0,e.r2=t.coords.r2||0),this.coords=e,this.colorStops=t.colorStops.slice(),t.gradientTransform&&(this.gradientTransform=t.gradientTransform),this.offsetX=t.offsetX||this.offsetX,this.offsetY=t.offsetY||this.offsetY},addColorStop:function(t){for(var e in t){var i=new fabric.Color(t[e]);this.colorStops.push({offset:e,color:i.toRgb(),opacity:i.getAlpha()})}return this},toObject:function(t){var e={type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY,gradientTransform:this.gradientTransform?this.gradientTransform.concat():this.gradientTransform};return fabric.util.populateWithProperties(this,e,t),e},toSVG:function(t){var e,i,r=fabric.util.object.clone(this.coords);if(this.colorStops.sort(function(t,e){return t.offset-e.offset}),!t.group||"path-group"!==t.group.type)for(var n in r)"x1"===n||"x2"===n||"r2"===n?r[n]+=this.offsetX-t.width/2:"y1"!==n&&"y2"!==n||(r[n]+=this.offsetY-t.height/2);i='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(i+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?e=["\n']:"radial"===this.type&&(e=["\n']);for(var s=0;s\n');return e.push("linear"===this.type?"\n":"\n"),e.join("")},toLive:function(t,e){var i,r,n=fabric.util.object.clone(this.coords);if(this.type){if(e.group&&"path-group"===e.group.type)for(r in n)"x1"===r||"x2"===r?n[r]+=-this.offsetX+e.width/2:"y1"!==r&&"y2"!==r||(n[r]+=-this.offsetY+e.height/2);"linear"===this.type?i=t.createLinearGradient(n.x1,n.y1,n.x2,n.y2):"radial"===this.type&&(i=t.createRadialGradient(n.x1,n.y1,n.r1,n.x2,n.y2,n.r2));for(var s=0,o=this.colorStops.length;s\n\n\n'},setOptions:function(t){for(var e in t)this[e]=t[e]},toLive:function(t){var e="function"==typeof this.source?this.source():this.source;if(!e)return"";if("undefined"!=typeof e.src){if(!e.complete)return"";if(0===e.naturalWidth||0===e.naturalHeight)return""}return t.createPattern(e,this.repeat)}})}(),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.toFixed;return e.Shadow?void e.warn("fabric.Shadow is already defined."):(e.Shadow=e.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(t){"string"==typeof t&&(t=this._parseShadow(t));for(var i in t)this[i]=t[i];this.id=e.Object.__uid++},_parseShadow:function(t){var i=t.trim(),r=e.Shadow.reOffsetsAndBlur.exec(i)||[],n=i.replace(e.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:n.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(t){var r=40,n=40,s=e.Object.NUM_FRACTION_DIGITS,o=e.util.rotateVector({x:this.offsetX,y:this.offsetY},e.util.degreesToRadians(-t.angle)),a=20;return t.width&&t.height&&(r=100*i((Math.abs(o.x)+this.blur)/t.width,s)+a,n=100*i((Math.abs(o.y)+this.blur)/t.height,s)+a),t.flipX&&(o.x*=-1),t.flipY&&(o.y*=-1),'\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\t\n\t\n\n'},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY,affectStroke:this.affectStroke};var t={},i=e.Shadow.prototype;return["color","blur","offsetX","offsetY","affectStroke"].forEach(function(e){this[e]!==i[e]&&(t[e]=this[e])},this),t}}),void(e.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/))}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)return void fabric.warn("fabric.StaticCanvas is already defined.");var t=fabric.util.object.extend,e=fabric.util.getElementOffset,i=fabric.util.removeFromArray,r=fabric.util.toFixed,n=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass(fabric.CommonMethods,{initialize:function(t,e){e||(e={}),this._initStatic(t,e)},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!1,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:[1,0,0,1,0,0],backgroundVpt:!0,overlayVpt:!0,onBeforeScaleRotate:function(){},enableRetinaScaling:!0,_initStatic:function(t,e){var i=fabric.StaticCanvas.prototype.renderAll.bind(this);this._objects=[],this._createLowerCanvas(t),this._initOptions(e),this._setImageSmoothing(),this.interactive||this._initRetinaScaling(),e.overlayImage&&this.setOverlayImage(e.overlayImage,i),e.backgroundImage&&this.setBackgroundImage(e.backgroundImage,i),e.backgroundColor&&this.setBackgroundColor(e.backgroundColor,i),e.overlayColor&&this.setOverlayColor(e.overlayColor,i),this.calcOffset()},_isRetinaScaling:function(){return 1!==fabric.devicePixelRatio&&this.enableRetinaScaling},getRetinaScaling:function(){return this._isRetinaScaling()?fabric.devicePixelRatio:1},_initRetinaScaling:function(){this._isRetinaScaling()&&(this.lowerCanvasEl.setAttribute("width",this.width*fabric.devicePixelRatio),this.lowerCanvasEl.setAttribute("height",this.height*fabric.devicePixelRatio),this.contextContainer.scale(fabric.devicePixelRatio,fabric.devicePixelRatio))},calcOffset:function(){return this._offset=e(this.lowerCanvasEl),this},setOverlayImage:function(t,e,i){return this.__setBgOverlayImage("overlayImage",t,e,i)},setBackgroundImage:function(t,e,i){return this.__setBgOverlayImage("backgroundImage",t,e,i)},setOverlayColor:function(t,e){return this.__setBgOverlayColor("overlayColor",t,e)},setBackgroundColor:function(t,e){return this.__setBgOverlayColor("backgroundColor",t,e)},_setImageSmoothing:function(){var t=this.getContext();t.imageSmoothingEnabled=t.imageSmoothingEnabled||t.webkitImageSmoothingEnabled||t.mozImageSmoothingEnabled||t.msImageSmoothingEnabled||t.oImageSmoothingEnabled,t.imageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(t,e,i,r){return"string"==typeof e?fabric.util.loadImage(e,function(e){e&&(this[t]=new fabric.Image(e,r)),i&&i(e)},this,r&&r.crossOrigin):(r&&e.setOptions(r),this[t]=e,i&&i(e)),this},__setBgOverlayColor:function(t,e,i){return this[t]=e,this._initGradient(e,t),this._initPattern(e,t,i),this},_createCanvasElement:function(t){var e=fabric.util.createCanvasElement(t);if(e.style||(e.style={}),!e)throw n;if("undefined"==typeof e.getContext)throw n;return e},_initOptions:function(t){this._setOptions(t),this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0,this.lowerCanvasEl.style&&(this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(t){this.lowerCanvasEl=fabric.util.getById(t)||this._createCanvasElement(t),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(t,e){return this.setDimensions({width:t},e)},setHeight:function(t,e){return this.setDimensions({height:t},e)},setDimensions:function(t,e){var i;e=e||{};for(var r in t)i=t[r],e.cssOnly||(this._setBackstoreDimension(r,t[r]),i+="px"),e.backstoreOnly||this._setCssDimension(r,i);return this._initRetinaScaling(),this._setImageSmoothing(),this.calcOffset(),e.cssOnly||this.renderAll(),this},_setBackstoreDimension:function(t,e){return this.lowerCanvasEl[t]=e,this.upperCanvasEl&&(this.upperCanvasEl[t]=e),this.cacheCanvasEl&&(this.cacheCanvasEl[t]=e),this[t]=e,this},_setCssDimension:function(t,e){return this.lowerCanvasEl.style[t]=e,this.upperCanvasEl&&(this.upperCanvasEl.style[t]=e),this.wrapperEl&&(this.wrapperEl.style[t]=e),this},getZoom:function(){return this.viewportTransform[0]},setViewportTransform:function(t){var e,i=this._activeGroup,r=!1,n=!0;this.viewportTransform=t;for(var s=0,o=this._objects.length;s"),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('\n','\n')},_setSVGHeader:function(t,e){var i,n=e.width||this.width,s=e.height||this.height,o='viewBox="0 0 '+this.width+" "+this.height+'" ',a=fabric.Object.NUM_FRACTION_DIGITS;e.viewBox?o='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,o='viewBox="'+r(-i[4]/i[0],a)+" "+r(-i[5]/i[3],a)+" "+r(this.width/i[0],a)+" "+r(this.height/i[3],a)+'" '),t.push("\n',"Created with Fabric.js ",fabric.version,"\n","\n",this.createSVGFontFacesMarkup(),this.createSVGRefElementsMarkup(),"\n")},createSVGRefElementsMarkup:function(){var t=this,e=["backgroundColor","overlayColor"].map(function(e){var i=t[e];if(i&&i.toLive)return i.toSVG(t,!1)});return e.join("")},createSVGFontFacesMarkup:function(){for(var t,e,i,r,n,s,o,a="",h={},c=fabric.fontPaths,l=this.getObjects(),u=0,f=l.length;u',"\n",a,"","\n"].join("")),a},_setSVGObjects:function(t,e){for(var i,r=0,n=this.getObjects(),s=n.length;r\n")}else t.push('\n")},sendToBack:function(t){if(!t)return this;var e,r,n,s=this._activeGroup;if(t===s)for(n=s._objects,e=n.length;e--;)r=n[e],i(this._objects,r),this._objects.unshift(r);else i(this._objects,t),this._objects.unshift(t);return this.renderAll&&this.renderAll()},bringToFront:function(t){if(!t)return this;var e,r,n,s=this._activeGroup;if(t===s)for(n=s._objects,e=0;e=0;--n){var s=t.intersectsWithObject(this._objects[n])||t.isContainedWithinObject(this._objects[n])||this._objects[n].isContainedWithinObject(t);if(s){r=n;break}}}else r=e-1;return r},bringForward:function(t,e){if(!t)return this;var r,n,s,o,a,h=this._activeGroup;if(t===h)for(a=h._objects,r=a.length;r--;)n=a[r],s=this._objects.indexOf(n),s!==this._objects.length-1&&(o=s+1,i(this._objects,n),this._objects.splice(o,0,n));else s=this._objects.indexOf(t),s!==this._objects.length-1&&(o=this._findNewUpperIndex(t,s,e),i(this._objects,t),this._objects.splice(o,0,t));return this.renderAll&&this.renderAll(),this},_findNewUpperIndex:function(t,e,i){var r;if(i){r=e;for(var n=e+1;n"}}),t(fabric.StaticCanvas.prototype,fabric.Observable),t(fabric.StaticCanvas.prototype,fabric.Collection),t(fabric.StaticCanvas.prototype,fabric.DataURLExporter),t(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(t){var e=fabric.util.createCanvasElement();if(!e||!e.getContext)return null;var i=e.getContext("2d");if(!i)return null;switch(t){case"getImageData":return"undefined"!=typeof i.getImageData;case"setLineDash":return"undefined"!=typeof i.setLineDash;case"toDataURL":return"undefined"!=typeof e.toDataURL;case"toDataURLWithQuality":try{return e.toDataURL("image/jpeg",0),!0}catch(t){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",strokeDashArray:null,setShadow:function(t){return this.shadow=new fabric.Shadow(t),this},_setBrushStyles:function(){var t=this.canvas.contextTop;t.strokeStyle=this.color,t.lineWidth=this.width,t.lineCap=this.strokeLineCap,t.lineJoin=this.strokeLineJoin,this.strokeDashArray&&fabric.StaticCanvas.supports("setLineDash")&&t.setLineDash(this.strokeDashArray)},_setShadow:function(){if(this.shadow){var t=this.canvas.contextTop,e=this.canvas.getZoom();t.shadowColor=this.shadow.color,t.shadowBlur=this.shadow.blur*e,t.shadowOffsetX=this.shadow.offsetX*e,t.shadowOffsetY=this.shadow.offsetY*e}},_resetShadow:function(){var t=this.canvas.contextTop;t.shadowColor="",t.shadowBlur=t.shadowOffsetX=t.shadowOffsetY=0}}),function(){fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(t){this.canvas=t,this._points=[]},onMouseDown:function(t){this._prepareForDrawing(t),this._captureDrawingPath(t),this._render()},onMouseMove:function(t){this._captureDrawingPath(t),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(t){var e=new fabric.Point(t.x,t.y);this._reset(),this._addPoint(e),this.canvas.contextTop.moveTo(e.x,e.y)},_addPoint:function(t){this._points.push(t)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(t){var e=new fabric.Point(t.x,t.y);this._addPoint(e)},_render:function(){var t=this.canvas.contextTop,e=this.canvas.viewportTransform,i=this._points[0],r=this._points[1];t.save(),t.transform(e[0],e[1],e[2],e[3],e[4],e[5]),t.beginPath(),2===this._points.length&&i.x===r.x&&i.y===r.y&&(i.x-=.5,r.x+=.5),t.moveTo(i.x,i.y);for(var n=1,s=this._points.length;n0?1:-1,"y"===i&&(s=e.target.skewY,o="top",a="bottom",r="originY"),n[-1]=o,n[1]=a,e.target.flipX&&(c*=-1),e.target.flipY&&(c*=-1),0===s?(e.skewSign=-h*t*c,e[r]=n[-t]):(s=s>0?1:-1,e.skewSign=s,e[r]=n[s*h*c])},_skewObject:function(t,e,i){var r=this._currentTransform,n=r.target,s=!1,o=n.get("lockSkewingX"),a=n.get("lockSkewingY");if(o&&"x"===i||a&&"y"===i)return!1;var h,c,l=n.getCenterPoint(),u=n.toLocalPoint(new fabric.Point(t,e),"center","center")[i],f=n.toLocalPoint(new fabric.Point(r.lastX,r.lastY),"center","center")[i],d=n._getTransformedDimensions();return this._changeSkewTransformOrigin(u-f,r,i),h=n.toLocalPoint(new fabric.Point(t,e),r.originX,r.originY)[i],c=n.translateToOriginPoint(l,r.originX,r.originY),s=this._setObjectSkew(h,r,i,d),r.lastX=t,r.lastY=e,n.setPositionByOrigin(c,r.originX,r.originY),s},_setObjectSkew:function(t,e,i,r){var n,s,o,a,h,c,l,u,f,d=e.target,g=!1,p=e.skewSign;return"x"===i?(a="y",h="Y",c="X",u=0,f=d.skewY):(a="x",h="X",c="Y",u=d.skewX,f=0),o=d._getTransformedDimensions(u,f),l=2*Math.abs(t)-o[i],l<=2?n=0:(n=p*Math.atan(l/d["scale"+c]/(o[a]/d["scale"+h])),n=fabric.util.radiansToDegrees(n)),g=d["skew"+c]!==n,d.set("skew"+c,n),0!==d["skew"+h]&&(s=d._getTransformedDimensions(),n=r[a]/s[a]*d["scale"+h],d.set("scale"+h,n)),g},_scaleObject:function(t,e,i){var r=this._currentTransform,n=r.target,s=n.get("lockScalingX"),o=n.get("lockScalingY"),a=n.get("lockScalingFlip");if(s&&o)return!1;var h=n.translateToOriginPoint(n.getCenterPoint(),r.originX,r.originY),c=n.toLocalPoint(new fabric.Point(t,e),r.originX,r.originY),l=n._getTransformedDimensions(),u=!1;return this._setLocalMouse(c,r),u=this._setObjectScale(c,r,s,o,i,a,l),n.setPositionByOrigin(h,r.originX,r.originY),u},_setObjectScale:function(t,e,i,r,n,s,o){var a,h,c,l,u=e.target,f=!1,d=!1,g=!1;return c=t.x*u.scaleX/o.x,l=t.y*u.scaleY/o.y,a=u.scaleX!==c,h=u.scaleY!==l,s&&c<=0&&ci.padding?t.x<0?t.x+=i.padding:t.x-=i.padding:t.x=0,n(t.y)>i.padding?t.y<0?t.y+=i.padding:t.y-=i.padding:t.y=0},_rotateObject:function(t,e){var n=this._currentTransform;if(n.target.get("lockRotation"))return!1;var s=r(n.ey-n.top,n.ex-n.left),o=r(e-n.top,t-n.left),a=i(o-s+n.theta),h=!0;if(a<0&&(a=360+a),a%=360,n.target.snapAngle>0){var c=n.target.snapAngle,l=n.target.snapThreshold||c,u=Math.ceil(a/c)*c,f=Math.floor(a/c)*c;Math.abs(a-f)0?0:-i),e.ey-(r>0?0:-r),a,h)),this.selectionLineWidth&&this.selectionBorderColor)if(t.lineWidth=this.selectionLineWidth,t.strokeStyle=this.selectionBorderColor,this.selectionDashArray.length>1&&!s){var c=e.ex+o-(i>0?0:a),l=e.ey+o-(r>0?0:h);t.beginPath(),fabric.util.drawDashedLine(t,c,l,c+a,l,this.selectionDashArray),fabric.util.drawDashedLine(t,c,l+h-1,c+a,l+h-1,this.selectionDashArray),fabric.util.drawDashedLine(t,c,l,c,l+h,this.selectionDashArray),fabric.util.drawDashedLine(t,c+a-1,l,c+a-1,l+h,this.selectionDashArray),t.closePath(),t.stroke()}else fabric.Object.prototype._setLineDash.call(this,t,this.selectionDashArray),t.strokeRect(e.ex+o-(i>0?0:a),e.ey+o-(r>0?0:h),a,h)},findTarget:function(t,e){if(!this.skipTargetFind){var i,r=!0,n=this.getPointer(t,r),s=this.getActiveGroup(),o=this.getActiveObject();if(s&&!e&&this._checkTarget(n,s))return this._fireOverOutEvents(s,t),s;if(o&&o._findTargetCorner(n))return this._fireOverOutEvents(o,t),o;if(o&&this._checkTarget(n,o)){if(!this.preserveObjectStacking)return this._fireOverOutEvents(o,t),o;i=o}this.targets=[];var a=this._searchPossibleTargets(this._objects,n);return t[this.altSelectionKey]&&a&&i&&a!==i&&(a=i),this._fireOverOutEvents(a,t),a}},_fireOverOutEvents:function(t,e){t?this._hoveredTarget!==t&&(this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget,e:e}),this._hoveredTarget.fire("mouseout")),this.fire("mouse:over",{target:t,e:e}),t.fire("mouseover"),this._hoveredTarget=t):this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget,e:e}),this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null)},_checkTarget:function(t,e){if(e&&e.visible&&e.evented&&this.containsPoint(null,e,t)){if(!this.perPixelTargetFind&&!e.perPixelTargetFind||e.isEditing)return!0;var i=this.isTargetTransparent(e,t.x,t.y);if(!i)return!0}},_searchPossibleTargets:function(t,e){for(var i,r,n,s=t.length;s--;)if(this._checkTarget(e,t[s])){i=t[s],"group"===i.type&&i.subTargetCheck&&(r=this._normalizePointer(i,e),n=this._searchPossibleTargets(i._objects,r),n&&this.targets.push(n));break}return i},restorePointerVpt:function(t){return fabric.util.transformPoint(t,fabric.util.invertTransform(this.viewportTransform))},getPointer:function(e,i,r){r||(r=this.upperCanvasEl);var n,s=t(e),o=r.getBoundingClientRect(),a=o.width||0,h=o.height||0;return a&&h||("top"in o&&"bottom"in o&&(h=Math.abs(o.top-o.bottom)),"right"in o&&"left"in o&&(a=Math.abs(o.right-o.left))),this.calcOffset(),s.x=s.x-this._offset.left,s.y=s.y-this._offset.top,i||(s=this.restorePointerVpt(s)),n=0===a||0===h?{width:1,height:1}:{width:r.width/a,height:r.height/h},{x:s.x*n.width,y:s.y*n.height}},_createUpperCanvas:function(){var t=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+t),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{class:this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(t){var e=this.getWidth()||t.width,i=this.getHeight()||t.height;fabric.util.setStyle(t,{position:"absolute",width:e+"px",height:i+"px",left:0,top:0}),t.width=e,t.height=i,fabric.util.makeElementUnselectable(t)},_copyCanvasStyle:function(t,e){e.style.cssText=t.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(t){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=t,t.set("active",!0)},setActiveObject:function(t,e){return this._setActiveObject(t),this.renderAll(),this.fire("object:selected",{target:t,e:e}),t.fire("selected",{e:e}),this},getActiveObject:function(){return this._activeObject},_onObjectRemoved:function(t){this.getActiveObject()===t&&(this.fire("before:selection:cleared",{target:t}),this._discardActiveObject(),this.fire("selection:cleared",{target:t}),t.fire("deselected")),this.callSuper("_onObjectRemoved",t)},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(t){var e=this._activeObject;return this.fire("before:selection:cleared",{target:e,e:t}),this._discardActiveObject(),this.fire("selection:cleared",{e:t}),e&&e.fire("deselected",{e:t}),this},_setActiveGroup:function(t){this._activeGroup=t,t&&t.set("active",!0)},setActiveGroup:function(t,e){return this._setActiveGroup(t),t&&(this.fire("object:selected",{target:t,e:e}),t.fire("selected",{e:e})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var t=this.getActiveGroup();t&&t.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(t){var e=this.getActiveGroup();return this.fire("before:selection:cleared",{e:t,target:e}),this._discardActiveGroup(),this.fire("selection:cleared",{e:t}),this},deactivateAll:function(){for(var t,e=this.getObjects(),i=0,r=e.length;i1)){var r=this._groupSelector;r?(i=this.getPointer(t,!0),r.left=i.x-r.ex,r.top=i.y-r.ey,this.renderTop()):this._currentTransform?this._transformObject(t):(e=this.findTarget(t),this._setCursorFromEvent(t,e)),this._handleEvent(t,"move",e?e:null)}},__onMouseWheel:function(t){this._handleEvent(t,"wheel")},_transformObject:function(t){var e=this.getPointer(t),i=this._currentTransform;i.reset=!1,i.target.isMoving=!0,i.shiftKey=t.shiftKey,i.altKey=t[this.centeredKey],this._beforeScaleTransform(t,i),this._performTransformAction(t,i,e),i.actionPerformed&&this.renderAll()},_performTransformAction:function(t,e,i){var r=i.x,n=i.y,s=e.target,o=e.action,a=!1;"rotate"===o?(a=this._rotateObject(r,n))&&this._fire("rotating",s,t):"scale"===o?(a=this._onScale(t,e,r,n))&&this._fire("scaling",s,t):"scaleX"===o?(a=this._scaleObject(r,n,"x"))&&this._fire("scaling",s,t):"scaleY"===o?(a=this._scaleObject(r,n,"y"))&&this._fire("scaling",s,t):"skewX"===o?(a=this._skewObject(r,n,"x"))&&this._fire("skewing",s,t):"skewY"===o?(a=this._skewObject(r,n,"y"))&&this._fire("skewing",s,t):(a=this._translateObject(r,n),a&&(this._fire("moving",s,t),this.setCursor(s.moveCursor||this.moveCursor))),e.actionPerformed=e.actionPerformed||a},_fire:function(t,e,i){this.fire("object:"+t,{target:e,e:i}),e.fire(t,{e:i})},_beforeScaleTransform:function(t,e){if("scale"===e.action||"scaleX"===e.action||"scaleY"===e.action){var i=this._shouldCenterTransform(e.target);(i&&("center"!==e.originX||"center"!==e.originY)||!i&&"center"===e.originX&&"center"===e.originY)&&(this._resetCurrentTransform(),e.reset=!0)}},_onScale:function(t,e,i,r){return!t[this.uniScaleKey]&&!this.uniScaleTransform||e.target.get("lockUniScaling")?(e.reset||"scale"!==e.currentAction||this._resetCurrentTransform(),e.currentAction="scaleEqually",this._scaleObject(i,r,"equally")):(e.currentAction="scale",this._scaleObject(i,r))},_setCursorFromEvent:function(t,e){if(!e)return this.setCursor(this.defaultCursor),!1;var i=e.hoverCursor||this.hoverCursor;if(e.selectable){var r=this.getActiveGroup(),n=e._findTargetCorner&&(!r||!r.contains(e))&&e._findTargetCorner(this.getPointer(t,!0));n?this._setCornerCursor(n,e,t):this.setCursor(i)}else this.setCursor(i);return!0},_setCornerCursor:function(e,i,r){if(e in t)this.setCursor(this._getRotatedCornerCursor(e,i,r));else{if("mtr"!==e||!i.hasRotatingPoint)return this.setCursor(this.defaultCursor),!1;this.setCursor(this.rotationCursor)}},_getRotatedCornerCursor:function(e,i,r){var n=Math.round(i.getAngle()%360/45);return n<0&&(n+=8),n+=t[e],r[this.altActionKey]&&t[e]%2===0&&(n+=2),n%=8,this.cursorMap[n]}})}(),function(){var t=Math.min,e=Math.max;fabric.util.object.extend(fabric.Canvas.prototype,{_shouldGroup:function(t,e){var i=this.getActiveObject();return t[this.selectionKey]&&e&&e.selectable&&(this.getActiveGroup()||i&&i!==e)&&this.selection},_handleGrouping:function(t,e){var i=this.getActiveGroup();(e!==i||(e=this.findTarget(t,!0)))&&(i?this._updateActiveGroup(e,t):this._createActiveGroup(e,t),this._activeGroup&&this._activeGroup.saveCoords())},_updateActiveGroup:function(t,e){var i=this.getActiveGroup();if(i.contains(t)){if(i.removeWithUpdate(t),t.set("active",!1),1===i.size())return this.discardActiveGroup(e),void this.setActiveObject(i.item(0))}else i.addWithUpdate(t);this.fire("selection:created",{target:i,e:e}),i.set("active",!0)},_createActiveGroup:function(t,e){if(this._activeObject&&t!==this._activeObject){var i=this._createGroup(t);i.addWithUpdate(),this.setActiveGroup(i),this._activeObject=null,this.fire("selection:created",{target:i,e:e})}t.set("active",!0)},_createGroup:function(t){var e=this.getObjects(),i=e.indexOf(this._activeObject)1&&(e=new fabric.Group(e.reverse(),{canvas:this}),e.addWithUpdate(),this.setActiveGroup(e,t),e.saveCoords(),this.fire("selection:created",{target:e}),this.renderAll())},_collectObjects:function(){for(var i,r=[],n=this._groupSelector.ex,s=this._groupSelector.ey,o=n+this._groupSelector.left,a=s+this._groupSelector.top,h=new fabric.Point(t(n,o),t(s,a)),c=new fabric.Point(e(n,o),e(s,a)),l=n===o&&s===a,u=this._objects.length;u--&&(i=this._objects[u],!(i&&i.selectable&&i.visible&&(i.intersectsWithRect(h,c)||i.isContainedWithinRect(h,c)||i.containsPoint(h)||i.containsPoint(c))&&(i.set("active",!0),r.push(i),l))););return r},_maybeGroupObjects:function(t){this.selection&&this._groupSelector&&this._groupSelectedObjects(t);var e=this.getActiveGroup();e&&(e.setObjectsCoords().setCoords(),e.isMoving=!1,this.setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),function(){var t=fabric.StaticCanvas.supports("toDataURLWithQuality");fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(t){t||(t={});var e=t.format||"png",i=t.quality||1,r=t.multiplier||1,n={left:t.left||0,top:t.top||0,width:t.width||0,height:t.height||0};return this.__toDataURLWithMultiplier(e,i,n,r)},__toDataURLWithMultiplier:function(t,e,i,r){var n=this.getWidth(),s=this.getHeight(),o=(i.width||this.getWidth())*r,a=(i.height||this.getHeight())*r,h=this.getZoom(),c=h*r,l=this.viewportTransform,u=(l[4]-i.left)*r,f=(l[5]-i.top)*r,d=[c,0,0,c,u,f],g=this.interactive;this.viewportTransform=d,this.interactive&&(this.interactive=!1),n!==o||s!==a?this.setDimensions({width:o,height:a}):this.renderAll();var p=this.__toDataURL(t,e,i);return g&&(this.interactive=g),this.viewportTransform=l,this.setDimensions({width:n,height:s}),p},__toDataURL:function(e,i){var r=this.contextContainer.canvas;"jpg"===e&&(e="jpeg");var n=t?r.toDataURL("image/"+e,i):r.toDataURL("image/"+e);return n},toDataURLWithMultiplier:function(t,e,i){return this.toDataURL({format:t,multiplier:e,quality:i})}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(t,e,i){return this.loadFromJSON(t,e,i)},loadFromJSON:function(t,e,i){if(t){var r="string"==typeof t?JSON.parse(t):fabric.util.object.clone(t);this.clear();var n=this;return this._enlivenObjects(r.objects,function(){n._setBgOverlay(r,function(){delete r.objects,delete r.backgroundImage,delete r.overlayImage,delete r.background,delete r.overlay,n._setOptions(r),e&&e()})},i),this}},_setBgOverlay:function(t,e){var i=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!(t.backgroundImage||t.overlayImage||t.background||t.overlay))return void(e&&e());var n=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(i.renderAll(),e&&e())};this.__setBgOverlay("backgroundImage",t.backgroundImage,r,n),this.__setBgOverlay("overlayImage",t.overlayImage,r,n),this.__setBgOverlay("backgroundColor",t.background,r,n),this.__setBgOverlay("overlayColor",t.overlay,r,n)},__setBgOverlay:function(t,e,i,r){var n=this;return e?void("backgroundImage"===t||"overlayImage"===t?fabric.util.enlivenObjects([e],function(e){n[t]=e[0],i[t]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(t,!0)](e,function(){i[t]=!0,r&&r()})):(i[t]=!0,void(r&&r()))},_enlivenObjects:function(t,e,i){var r=this;if(!t||0===t.length)return void(e&&e());var n=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(t,function(t){t.forEach(function(t,e){r.insertAt(t,e)}),r.renderOnAddRemove=n,e&&e()},null,i)},_toDataURL:function(t,e){this.clone(function(i){e(i.toDataURL(t))})},_toDataURLWithMultiplier:function(t,e,i){this.clone(function(r){i(r.toDataURLWithMultiplier(t,e))})},clone:function(t,e){var i=JSON.stringify(this.toJSON(e));this.cloneWithoutData(function(e){e.loadFromJSON(i,function(){t&&t(e)})})},cloneWithoutData:function(t){var e=fabric.document.createElement("canvas");e.width=this.getWidth(),e.height=this.getHeight();var i=new fabric.Canvas(e);i.clipTo=this.clipTo,this.backgroundImage?(i.setBackgroundImage(this.backgroundImage.src,function(){i.renderAll(),t&&t(i)}),i.backgroundImageOpacity=this.backgroundImageOpacity,i.backgroundImageStretch=this.backgroundImageStretch):t&&t(i)}}),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.util.object.clone,n=e.util.toFixed,s=e.util.string.capitalize,o=e.util.degreesToRadians,a=e.StaticCanvas.supports("setLineDash"),h=!e.isLikelyNode;e.Object||(e.Object=e.util.createClass(e.CommonMethods,{type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,skewX:0,skewY:0,cornerSize:13,transparentCorners:!0,hoverCursor:null,moveCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",borderDashArray:null,cornerColor:"rgba(102,153,255,0.5)",cornerStrokeColor:null,cornerStyle:"rect",cornerDashArray:null,centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",selectionBackgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockSkewingX:!1,lockSkewingY:!1,lockScalingFlip:!1,excludeFromExport:!1,objectCaching:h,statefullCache:!1,noScaleCache:!0,dirty:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor skewX skewY".split(" "),cacheProperties:"fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor".split(" "),initialize:function(t){t=t||{},t&&this.setOptions(t),this.objectCaching&&(this._createCacheCanvas(),this.setupState({propertySet:"cacheProperties"}))},_createCacheCanvas:function(){this._cacheCanvas=e.document.createElement("canvas"),this._cacheContext=this._cacheCanvas.getContext("2d"),this._updateCacheCanvas()},_getCacheCanvasDimensions:function(){var t=this.canvas&&this.canvas.getZoom()||1,i=this.getObjectScaling(),r=this._getNonTransformedDimensions(),n=this.canvas&&this.canvas._isRetinaScaling()?e.devicePixelRatio:1,s=i.scaleX*t*n,o=i.scaleY*t*n,a=r.x*s,h=r.y*o;return{width:Math.ceil(a)+2,height:Math.ceil(h)+2,zoomX:s,zoomY:o}},_updateCacheCanvas:function(){if(this.noScaleCache&&this.canvas&&this.canvas._currentTransform){var t=this.canvas._currentTransform.action;if("scale"===t.slice(0,5))return!1}var e=this._getCacheCanvasDimensions(),i=e.width,r=e.height,n=e.zoomX,s=e.zoomY;return(i!==this.cacheWidth||r!==this.cacheHeight)&&(this._cacheCanvas.width=i,this._cacheCanvas.height=r,this._cacheContext.translate(i/2,r/2),this._cacheContext.scale(n,s),this.cacheWidth=i,this.cacheHeight=r,this.zoomX=n,this.zoomY=s,!0)},setOptions:function(t){this._setOptions(t),this._initGradient(t.fill,"fill"),this._initGradient(t.stroke,"stroke"),this._initClipping(t),this._initPattern(t.fill,"fill"),this._initPattern(t.stroke,"stroke")},transform:function(t,e){this.group&&!this.group._transformDone&&this.group===this.canvas._activeGroup&&this.group.transform(t);var i=e?this._getLeftTopCoords():this.getCenterPoint();t.translate(i.x,i.y),t.rotate(o(this.angle)),t.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1)),t.transform(1,0,Math.tan(o(this.skewX)),1,0,0),t.transform(1,Math.tan(o(this.skewY)),0,1,0,0)},toObject:function(t){var i=e.Object.NUM_FRACTION_DIGITS,r={type:this.type,originX:this.originX,originY:this.originY,left:n(this.left,i),top:n(this.top,i),width:n(this.width,i),height:n(this.height,i),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:n(this.strokeWidth,i),strokeDashArray:this.strokeDashArray?this.strokeDashArray.concat():this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:n(this.strokeMiterLimit,i),scaleX:n(this.scaleX,i),scaleY:n(this.scaleY,i),angle:n(this.getAngle(),i),flipX:this.flipX,flipY:this.flipY,opacity:n(this.opacity,i),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,globalCompositeOperation:this.globalCompositeOperation,transformMatrix:this.transformMatrix?this.transformMatrix.concat():null,skewX:n(this.skewX,i),skewY:n(this.skewY,i)};return e.util.populateWithProperties(this,r,t),this.includeDefaultValues||(r=this._removeDefaultValues(r)),r},toDatalessObject:function(t){return this.toObject(t)},_removeDefaultValues:function(t){var i=e.util.getKlass(t.type).prototype,r=i.stateProperties;return r.forEach(function(e){t[e]===i[e]&&delete t[e];var r="[object Array]"===Object.prototype.toString.call(t[e])&&"[object Array]"===Object.prototype.toString.call(i[e]);r&&0===t[e].length&&0===i[e].length&&delete t[e]}),t},toString:function(){return"#"},getObjectScaling:function(){var t=this.scaleX,e=this.scaleY;if(this.group){var i=this.group.getObjectScaling();t*=i.scaleX,e*=i.scaleY}return{scaleX:t,scaleY:e}},_set:function(t,i){var r="scaleX"===t||"scaleY"===t;return r&&(i=this._constrainScale(i)),"scaleX"===t&&i<0?(this.flipX=!this.flipX,i*=-1):"scaleY"===t&&i<0?(this.flipY=!this.flipY,i*=-1):"shadow"!==t||!i||i instanceof e.Shadow?"dirty"===t&&this.group&&this.group.set("dirty",i):i=new e.Shadow(i),this[t]=i,this.cacheProperties.indexOf(t)>-1&&(this.group&&this.group.set("dirty",!0),this.dirty=!0),"width"!==t&&"height"!==t||(this.minScaleLimit=Math.min(.1,1/Math.max(this.width,this.height))),this},setOnGroup:function(){},setSourcePath:function(t){return this.sourcePath=t,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(t,i){0===this.width&&0===this.height||!this.visible||(t.save(),this._setupCompositeOperation(t),this.drawSelectionBackground(t),i||this.transform(t),this._setOpacity(t),this._setShadow(t),this.transformMatrix&&t.transform.apply(t,this.transformMatrix),this.clipTo&&e.util.clipContext(this,t),this.objectCaching&&!this.group?(this.isCacheDirty(i)&&(this.statefullCache&&this.saveState({propertySet:"cacheProperties"}),this.drawObject(this._cacheContext,i),this.dirty=!1),this.drawCacheOnCanvas(t)):(this.drawObject(t,i),i&&this.objectCaching&&this.statefullCache&&this.saveState({propertySet:"cacheProperties"})),this.clipTo&&t.restore(),t.restore())},drawObject:function(t,e){this._renderBackground(t),this._setStrokeStyles(t),this._setFillStyles(t),this._render(t,e)},drawCacheOnCanvas:function(t){t.scale(1/this.zoomX,1/this.zoomY),t.drawImage(this._cacheCanvas,-this.cacheWidth/2,-this.cacheHeight/2)},isCacheDirty:function(t){if(!t&&this._updateCacheCanvas())return!0;if(this.dirty||this.statefullCache&&this.hasStateChanged("cacheProperties")){if(!t){var e=this._getNonTransformedDimensions();this._cacheContext.clearRect(-e.x/2,-e.y/2,e.x,e.y)}return!0}return!1},_renderBackground:function(t){if(this.backgroundColor){var e=this._getNonTransformedDimensions();t.fillStyle=this.backgroundColor,t.fillRect(-e.x/2,-e.y/2,e.x,e.y),this._removeShadow(t)}},_setOpacity:function(t){t.globalAlpha*=this.opacity},_setStrokeStyles:function(t){this.stroke&&(t.lineWidth=this.strokeWidth,t.lineCap=this.strokeLineCap,t.lineJoin=this.strokeLineJoin,t.miterLimit=this.strokeMiterLimit,t.strokeStyle=this.stroke.toLive?this.stroke.toLive(t,this):this.stroke)},_setFillStyles:function(t){this.fill&&(t.fillStyle=this.fill.toLive?this.fill.toLive(t,this):this.fill)},_setLineDash:function(t,e,i){e&&(1&e.length&&e.push.apply(e,e),a?t.setLineDash(e):i&&i(t))},_renderControls:function(t,i){if(!(!this.active||i||this.group&&this.group!==this.canvas.getActiveGroup())){var r,n=this.getViewportTransform(),s=this.calcTransformMatrix();s=e.util.multiplyTransformMatrices(n,s),r=e.util.qrDecompose(s),t.save(),t.translate(r.translateX,r.translateY),t.lineWidth=1*this.borderScaleFactor,this.group||(t.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1),this.group&&this.group===this.canvas.getActiveGroup()?(t.rotate(o(r.angle)),this.drawBordersInGroup(t,r)):(t.rotate(o(this.angle)),this.drawBorders(t)),this.drawControls(t),t.restore()}},_setShadow:function(t){if(this.shadow){var i=this.canvas&&this.canvas.viewportTransform[0]||1,r=this.canvas&&this.canvas.viewportTransform[3]||1,n=this.getObjectScaling();this.canvas&&this.canvas._isRetinaScaling()&&(i*=e.devicePixelRatio,r*=e.devicePixelRatio),t.shadowColor=this.shadow.color,t.shadowBlur=this.shadow.blur*(i+r)*(n.scaleX+n.scaleY)/4,t.shadowOffsetX=this.shadow.offsetX*i*n.scaleX,t.shadowOffsetY=this.shadow.offsetY*r*n.scaleY}},_removeShadow:function(t){this.shadow&&(t.shadowColor="",t.shadowBlur=t.shadowOffsetX=t.shadowOffsetY=0)},_applyPatternGradientTransform:function(t,e){if(e.toLive){var i=e.gradientTransform||e.patternTransform;i&&t.transform.apply(t,i);var r=-this.width/2+e.offsetX||0,n=-this.height/2+e.offsetY||0;t.translate(r,n)}},_renderFill:function(t){this.fill&&(t.save(),this._applyPatternGradientTransform(t,this.fill),"evenodd"===this.fillRule?t.fill("evenodd"):t.fill(),t.restore())},_renderStroke:function(t){this.stroke&&0!==this.strokeWidth&&(this.shadow&&!this.shadow.affectStroke&&this._removeShadow(t),t.save(),this._setLineDash(t,this.strokeDashArray,this._renderDashedStroke),this._applyPatternGradientTransform(t,this.stroke),t.stroke(),t.restore())},clone:function(t,i){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(i),t):new e.Object(this.toObject(i))},cloneAsImage:function(t,i){var r=this.toDataURL(i);return e.util.loadImage(r,function(i){t&&t(new e.Image(i))}),this},toDataURL:function(t){t||(t={});var i=e.util.createCanvasElement(),r=this.getBoundingRect();i.width=r.width,i.height=r.height,e.util.wrapElement(i,"div");var n=new e.StaticCanvas(i,{enableRetinaScaling:t.enableRetinaScaling});"jpg"===t.format&&(t.format="jpeg"),"jpeg"===t.format&&(n.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new e.Point(n.getWidth()/2,n.getHeight()/2),"center","center");var o=this.canvas;n.add(this);var a=n.toDataURL(t);return this.set(s).setCoords(),this.canvas=o,n.dispose(),n=null,a},isType:function(t){return this.type===t},complexity:function(){return 0},toJSON:function(t){return this.toObject(t)},setGradient:function(t,i){i||(i={});var r={colorStops:[]};return r.type=i.type||(i.r1||i.r2?"radial":"linear"),r.coords={x1:i.x1,y1:i.y1,x2:i.x2,y2:i.y2},(i.r1||i.r2)&&(r.coords.r1=i.r1,r.coords.r2=i.r2),r.gradientTransform=i.gradientTransform,e.Gradient.prototype.addColorStop.call(r,i.colorStops),this.set(t,e.Gradient.forObject(this,r))},setPatternFill:function(t){return this.set("fill",new e.Pattern(t))},setShadow:function(t){return this.set("shadow",t?new e.Shadow(t):null)},setColor:function(t){return this.set("fill",t),this},setAngle:function(t){var e=("center"!==this.originX||"center"!==this.originY)&&this.centeredRotation;return e&&this._setOriginToCenter(),this.set("angle",t),e&&this._resetOrigin(),this},centerH:function(){return this.canvas&&this.canvas.centerObjectH(this),this},viewportCenterH:function(){return this.canvas&&this.canvas.viewportCenterObjectH(this),this},centerV:function(){return this.canvas&&this.canvas.centerObjectV(this),this},viewportCenterV:function(){return this.canvas&&this.canvas.viewportCenterObjectV(this),this},center:function(){return this.canvas&&this.canvas.centerObject(this),this},viewportCenter:function(){return this.canvas&&this.canvas.viewportCenterObject(this),this},remove:function(){return this.canvas&&this.canvas.remove(this),this},getLocalPointer:function(t,i){i=i||this.canvas.getPointer(t);var r=new e.Point(i.x,i.y),n=this._getLeftTopCoords();return this.angle&&(r=e.util.rotatePoint(r,n,o(-this.angle))),{x:r.x-n.x,y:r.y-n.y}},_setupCompositeOperation:function(t){this.globalCompositeOperation&&(t.globalCompositeOperation=this.globalCompositeOperation)}}),e.util.createAccessors(e.Object),e.Object.prototype.rotate=e.Object.prototype.setAngle,i(e.Object.prototype,e.Observable),e.Object.NUM_FRACTION_DIGITS=2,e.Object._fromObject=function(t,i,n,s,o){var a=e[t];if(i=r(i,!0),!s){var h=o?new a(i[o],i):new a(i);return n&&n(h),h}e.util.enlivenPatterns([i.fill,i.stroke],function(t){i.fill=t[0],i.stroke=t[1];var e=o?new a(i[o],i):new a(i);n&&n(e)})},e.Object.__uid=0)}("undefined"!=typeof exports?exports:this),function(){var t=fabric.util.degreesToRadians,e={left:-.5,center:0,right:.5},i={top:-.5,center:0,bottom:.5};fabric.util.object.extend(fabric.Object.prototype,{translateToGivenOrigin:function(t,r,n,s,o){var a,h,c,l=t.x,u=t.y;return"string"==typeof r?r=e[r]:r-=.5,"string"==typeof s?s=e[s]:s-=.5,a=s-r,"string"==typeof n?n=i[n]:n-=.5,"string"==typeof o?o=i[o]:o-=.5,h=o-n,(a||h)&&(c=this._getTransformedDimensions(),l=t.x+a*c.x,u=t.y+h*c.y),new fabric.Point(l,u)},translateToCenterPoint:function(e,i,r){var n=this.translateToGivenOrigin(e,i,r,"center","center");return this.angle?fabric.util.rotatePoint(n,e,t(this.angle)):n},translateToOriginPoint:function(e,i,r){var n=this.translateToGivenOrigin(e,"center","center",i,r);return this.angle?fabric.util.rotatePoint(n,e,t(this.angle)):n},getCenterPoint:function(){var t=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(t,this.originX,this.originY)},getPointByOrigin:function(t,e){var i=this.getCenterPoint();return this.translateToOriginPoint(i,t,e)},toLocalPoint:function(e,i,r){var n,s,o=this.getCenterPoint();return n="undefined"!=typeof i&&"undefined"!=typeof r?this.translateToGivenOrigin(o,"center","center",i,r):new fabric.Point(this.left,this.top),s=new fabric.Point(e.x,e.y),this.angle&&(s=fabric.util.rotatePoint(s,o,-t(this.angle))),s.subtractEquals(n)},setPositionByOrigin:function(t,e,i){var r=this.translateToCenterPoint(t,e,i),n=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",n.x),this.set("top",n.y)},adjustPosition:function(i){var r,n,s=t(this.angle),o=this.getWidth(),a=Math.cos(s)*o,h=Math.sin(s)*o;r="string"==typeof this.originX?e[this.originX]:this.originX-.5,n="string"==typeof i?e[i]:i-.5,this.left+=a*(n-r),this.top+=h*(n-r),this.setCoords(),this.originX=i},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var t=this.getCenterPoint();this.originX="center",this.originY="center",this.left=t.x,this.top=t.y},_resetOrigin:function(){var t=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=t.x,this.top=t.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","top")}})}(),function(){function t(t){return[new fabric.Point(t.tl.x,t.tl.y),new fabric.Point(t.tr.x,t.tr.y),new fabric.Point(t.br.x,t.br.y),new fabric.Point(t.bl.x,t.bl.y)]}var e=fabric.util.degreesToRadians,i=fabric.util.multiplyTransformMatrices;fabric.util.object.extend(fabric.Object.prototype,{ -oCoords:null,intersectsWithRect:function(e,i){var r=t(this.oCoords),n=fabric.Intersection.intersectPolygonRectangle(r,e,i);return"Intersection"===n.status},intersectsWithObject:function(e){var i=fabric.Intersection.intersectPolygonPolygon(t(this.oCoords),t(e.oCoords));return"Intersection"===i.status||e.isContainedWithinObject(this)||this.isContainedWithinObject(e)},isContainedWithinObject:function(e){for(var i=t(this.oCoords),r=0;r<4;r++)if(!e.containsPoint(i[r]))return!1;return!0},isContainedWithinRect:function(t,e){var i=this.getBoundingRect();return i.left>=t.x&&i.left+i.width<=e.x&&i.top>=t.y&&i.top+i.height<=e.y},containsPoint:function(t){this.oCoords||this.setCoords();var e=this._getImageLines(this.oCoords),i=this._findCrossPoints(t,e);return 0!==i&&i%2===1},_getImageLines:function(t){return{topline:{o:t.tl,d:t.tr},rightline:{o:t.tr,d:t.br},bottomline:{o:t.br,d:t.bl},leftline:{o:t.bl,d:t.tl}}},_findCrossPoints:function(t,e){var i,r,n,s,o,a,h=0;for(var c in e)if(a=e[c],!(a.o.y=t.y&&a.d.y>=t.y||(a.o.x===a.d.x&&a.o.x>=t.x?o=a.o.x:(i=0,r=(a.d.y-a.o.y)/(a.d.x-a.o.x),n=t.y-i*t.x,s=a.o.y-r*a.o.x,o=-(n-s)/(i-r)),o>=t.x&&(h+=1),2!==h)))break;return h},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(t){var e=this.calcCoords(t);return fabric.util.makeBoundingBoxFromPoints([e.tl,e.tr,e.br,e.bl])},getWidth:function(){return this._getTransformedDimensions().x},getHeight:function(){return this._getTransformedDimensions().y},_constrainScale:function(t){return Math.abs(t)0?Math.atan(o/s):0,l=s/Math.cos(c)/2,u=Math.cos(c+i)*l,f=Math.sin(c+i)*l,d=this.getCenterPoint(),g=t?d:fabric.util.transformPoint(d,r),p=new fabric.Point(g.x-u,g.y-f),v=new fabric.Point(p.x+s*h,p.y+s*a),b=new fabric.Point(p.x-o*a,p.y+o*h),m=new fabric.Point(g.x+u,g.y+f);if(!t)var y=new fabric.Point((p.x+b.x)/2,(p.y+b.y)/2),_=new fabric.Point((v.x+p.x)/2,(v.y+p.y)/2),x=new fabric.Point((m.x+v.x)/2,(m.y+v.y)/2),C=new fabric.Point((m.x+b.x)/2,(m.y+b.y)/2),S=new fabric.Point(_.x+a*this.rotatingPointOffset,_.y-h*this.rotatingPointOffset);var g={tl:p,tr:v,br:m,bl:b};return t||(g.ml=y,g.mt=_,g.mr=x,g.mb=C,g.mtr=S),g},setCoords:function(t,e){return this.oCoords=this.calcCoords(t),e||t||(this.absoluteCoords=this.calcCoords(!0)),t||this._setCornerCoords&&this._setCornerCoords(),this},_calcRotateMatrix:function(){if(this.angle){var t=e(this.angle),i=Math.cos(t),r=Math.sin(t);return[i,r,-r,i,0,0]}return[1,0,0,1,0,0]},calcTransformMatrix:function(){var t=this.getCenterPoint(),e=[1,0,0,1,t.x,t.y],r=this._calcRotateMatrix(),n=this._calcDimensionsTransformMatrix(this.skewX,this.skewY,!0),s=this.group?this.group.calcTransformMatrix():[1,0,0,1,0,0];return s=i(s,e),s=i(s,r),s=i(s,n)},_calcDimensionsTransformMatrix:function(t,r,n){var s=[1,0,Math.tan(e(t)),1],o=[1,Math.tan(e(r)),0,1],a=this.scaleX*(n&&this.flipX?-1:1),h=this.scaleY*(n&&this.flipY?-1:1),c=[a,0,0,h],l=i(c,s,!0);return i(l,o,!0)}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(t){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,t):this.canvas.sendBackwards(this,t),this},bringForward:function(t){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,t):this.canvas.bringForward(this,t),this},moveTo:function(t){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,t):this.canvas.moveTo(this,t),this}}),function(){function t(t,e){if(e){if(e.toLive)return t+": url(#SVGID_"+e.id+"); ";var i=new fabric.Color(e),r=t+": "+i.toRgb()+"; ",n=i.getAlpha();return 1!==n&&(r+=t+"-opacity: "+n.toString()+"; "),r}return t+": none; "}fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(e){var i=this.fillRule,r=this.strokeWidth?this.strokeWidth:"0",n=this.strokeDashArray?this.strokeDashArray.join(" "):"none",s=this.strokeLineCap?this.strokeLineCap:"butt",o=this.strokeLineJoin?this.strokeLineJoin:"miter",a=this.strokeMiterLimit?this.strokeMiterLimit:"4",h="undefined"!=typeof this.opacity?this.opacity:"1",c=this.visible?"":" visibility: hidden;",l=e?"":this.getSvgFilter(),u=t("fill",this.fill),f=t("stroke",this.stroke);return[f,"stroke-width: ",r,"; ","stroke-dasharray: ",n,"; ","stroke-linecap: ",s,"; ","stroke-linejoin: ",o,"; ","stroke-miterlimit: ",a,"; ",u,"fill-rule: ",i,"; ","opacity: ",h,";",l,c].join("")},getSvgFilter:function(){return this.shadow?"filter: url(#SVGID_"+this.shadow.id+");":""},getSvgId:function(){return this.id?'id="'+this.id+'" ':""},getSvgTransform:function(){if(this.group&&"path-group"===this.group.type)return"";var t=fabric.util.toFixed,e=this.getAngle(),i=this.getSkewX()%360,r=this.getSkewY()%360,n=this.getCenterPoint(),s=fabric.Object.NUM_FRACTION_DIGITS,o="path-group"===this.type?"":"translate("+t(n.x,s)+" "+t(n.y,s)+")",a=0!==e?" rotate("+t(e,s)+")":"",h=1===this.scaleX&&1===this.scaleY?"":" scale("+t(this.scaleX,s)+" "+t(this.scaleY,s)+")",c=0!==i?" skewX("+t(i,s)+")":"",l=0!==r?" skewY("+t(r,s)+")":"",u="path-group"===this.type?this.width:0,f=this.flipX?" matrix(-1 0 0 1 "+u+" 0) ":"",d="path-group"===this.type?this.height:0,g=this.flipY?" matrix(1 0 0 -1 0 "+d+")":"";return[o,a,h,f,g,c,l].join("")},getSvgTransformMatrix:function(){return this.transformMatrix?" matrix("+this.transformMatrix.join(" ")+") ":""},_createBaseSVGMarkup:function(){var t=[];return this.fill&&this.fill.toLive&&t.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&t.push(this.stroke.toSVG(this,!1)),this.shadow&&t.push(this.shadow.toSVG(this)),t}})}(),function(){function t(t,e,r){var n={},s=!0;r.forEach(function(e){n[e]=t[e]}),i(t[e],n,s)}function e(t,i,r){if(!fabric.isLikelyNode&&t instanceof Element)return t===i;if(t instanceof Array){if(t.length!==i.length)return!1;for(var n=0,s=t.length;n\n'),t?t(e.join("")):e.join("")},complexity:function(){return 1}}),i.Line.ATTRIBUTE_NAMES=i.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),i.Line.fromElement=function(t,e){e=e||{};var n=i.parseAttributes(t,i.Line.ATTRIBUTE_NAMES),s=[n.x1||0,n.y1||0,n.x2||0,n.y2||0];return e.originX="left",e.originY="top",new i.Line(s,r(n,e))},i.Line.fromObject=function(t,e,r){function s(t){delete t.points,e&&e(t)}var o=n(t,!0);o.points=[t.x1,t.y1,t.x2,t.y2];var a=i.Object._fromObject("Line",o,s,r,"points");return a&&delete a.points,a}}("undefined"!=typeof exports?exports:this),function(t){"use strict";function e(t){return"radius"in t&&t.radius>=0}var i=t.fabric||(t.fabric={}),r=Math.PI,n=i.util.object.extend;if(i.Circle)return void i.warn("fabric.Circle is already defined.");var s=i.Object.prototype.cacheProperties.concat();s.push("radius"),i.Circle=i.util.createClass(i.Object,{type:"circle",radius:0,startAngle:0,endAngle:2*r,cacheProperties:s,initialize:function(t){this.callSuper("initialize",t),this.set("radius",t&&t.radius||0)},_set:function(t,e){return this.callSuper("_set",t,e),"radius"===t&&this.setRadius(e),this},toObject:function(t){return this.callSuper("toObject",["radius","startAngle","endAngle"].concat(t))},toSVG:function(t){var e=this._createBaseSVGMarkup(),i=0,n=0,s=(this.endAngle-this.startAngle)%(2*r);if(0===s)this.group&&"path-group"===this.group.type&&(i=this.left+this.radius,n=this.top+this.radius),e.push("\n');else{var o=Math.cos(this.startAngle)*this.radius,a=Math.sin(this.startAngle)*this.radius,h=Math.cos(this.endAngle)*this.radius,c=Math.sin(this.endAngle)*this.radius,l=s>r?"1":"0";e.push('\n')}return t?t(e.join("")):e.join("")},_render:function(t,e){t.beginPath(),t.arc(e?this.left+this.radius:0,e?this.top+this.radius:0,this.radius,this.startAngle,this.endAngle,!1),this._renderFill(t),this._renderStroke(t)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(t){return this.radius=t,this.set("width",2*t).set("height",2*t)},complexity:function(){return 1}}),i.Circle.ATTRIBUTE_NAMES=i.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),i.Circle.fromElement=function(t,r){r||(r={});var s=i.parseAttributes(t,i.Circle.ATTRIBUTE_NAMES);if(!e(s))throw new Error("value of `r` attribute is required and can not be negative");s.left=s.left||0,s.top=s.top||0;var o=new i.Circle(n(s,r));return o.left-=o.radius,o.top-=o.radius,o},i.Circle.fromObject=function(t,e,r){return i.Object._fromObject("Circle",t,e,r)}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={});return e.Triangle?void e.warn("fabric.Triangle is already defined"):(e.Triangle=e.util.createClass(e.Object,{type:"triangle",initialize:function(t){this.callSuper("initialize",t),this.set("width",t&&t.width||100).set("height",t&&t.height||100)},_render:function(t){var e=this.width/2,i=this.height/2;t.beginPath(),t.moveTo(-e,i),t.lineTo(0,-i),t.lineTo(e,i),t.closePath(),this._renderFill(t),this._renderStroke(t)},_renderDashedStroke:function(t){var i=this.width/2,r=this.height/2;t.beginPath(),e.util.drawDashedLine(t,-i,r,0,-r,this.strokeDashArray),e.util.drawDashedLine(t,0,-r,i,r,this.strokeDashArray),e.util.drawDashedLine(t,i,r,-i,r,this.strokeDashArray),t.closePath()},toSVG:function(t){var e=this._createBaseSVGMarkup(),i=this.width/2,r=this.height/2,n=[-i+" "+r,"0 "+-r,i+" "+r].join(",");return e.push("'),t?t(e.join("")):e.join("")},complexity:function(){return 1}}),void(e.Triangle.fromObject=function(t,i,r){return e.Object._fromObject("Triangle",t,i,r)}))}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=2*Math.PI,r=e.util.object.extend;if(e.Ellipse)return void e.warn("fabric.Ellipse is already defined.");var n=e.Object.prototype.cacheProperties.concat();n.push("rx","ry"),e.Ellipse=e.util.createClass(e.Object,{type:"ellipse",rx:0,ry:0,cacheProperties:n,initialize:function(t){this.callSuper("initialize",t),this.set("rx",t&&t.rx||0),this.set("ry",t&&t.ry||0)},_set:function(t,e){switch(this.callSuper("_set",t,e),t){case"rx":this.rx=e,this.set("width",2*e);break;case"ry":this.ry=e,this.set("height",2*e)}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(t){return this.callSuper("toObject",["rx","ry"].concat(t))},toSVG:function(t){var e=this._createBaseSVGMarkup(),i=0,r=0;return this.group&&"path-group"===this.group.type&&(i=this.left+this.rx,r=this.top+this.ry),e.push("\n'),t?t(e.join("")):e.join("")},_render:function(t,e){t.beginPath(),t.save(),t.transform(1,0,0,this.ry/this.rx,0,0),t.arc(e?this.left+this.rx:0,e?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,i,!1),t.restore(),this._renderFill(t),this._renderStroke(t)},complexity:function(){return 1}}),e.Ellipse.ATTRIBUTE_NAMES=e.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),e.Ellipse.fromElement=function(t,i){i||(i={});var n=e.parseAttributes(t,e.Ellipse.ATTRIBUTE_NAMES);n.left=n.left||0,n.top=n.top||0;var s=new e.Ellipse(r(n,i));return s.top-=s.ry,s.left-=s.rx,s},e.Ellipse.fromObject=function(t,i,r){return e.Object._fromObject("Ellipse",t,i,r)}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend;if(e.Rect)return void e.warn("fabric.Rect is already defined");var r=e.Object.prototype.stateProperties.concat();r.push("rx","ry"),e.Rect=e.util.createClass(e.Object,{stateProperties:r,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(t){this.callSuper("initialize",t),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(t,e){if(1===this.width&&1===this.height)return void t.fillRect(-.5,-.5,1,1);var i=this.rx?Math.min(this.rx,this.width/2):0,r=this.ry?Math.min(this.ry,this.height/2):0,n=this.width,s=this.height,o=e?this.left:-this.width/2,a=e?this.top:-this.height/2,h=0!==i||0!==r,c=.4477152502;t.beginPath(),t.moveTo(o+i,a),t.lineTo(o+n-i,a),h&&t.bezierCurveTo(o+n-c*i,a,o+n,a+c*r,o+n,a+r),t.lineTo(o+n,a+s-r),h&&t.bezierCurveTo(o+n,a+s-c*r,o+n-c*i,a+s,o+n-i,a+s),t.lineTo(o+i,a+s),h&&t.bezierCurveTo(o+c*i,a+s,o,a+s-c*r,o,a+s-r),t.lineTo(o,a+r),h&&t.bezierCurveTo(o,a+c*r,o+c*i,a,o+i,a),t.closePath(),this._renderFill(t),this._renderStroke(t)},_renderDashedStroke:function(t){var i=-this.width/2,r=-this.height/2,n=this.width,s=this.height;t.beginPath(),e.util.drawDashedLine(t,i,r,i+n,r,this.strokeDashArray),e.util.drawDashedLine(t,i+n,r,i+n,r+s,this.strokeDashArray),e.util.drawDashedLine(t,i+n,r+s,i,r+s,this.strokeDashArray),e.util.drawDashedLine(t,i,r+s,i,r,this.strokeDashArray),t.closePath()},toObject:function(t){return this.callSuper("toObject",["rx","ry"].concat(t))},toSVG:function(t){var e=this._createBaseSVGMarkup(),i=this.left,r=this.top;return this.group&&"path-group"===this.group.type||(i=-this.width/2,r=-this.height/2),e.push("\n'),t?t(e.join("")):e.join("")},complexity:function(){return 1}}),e.Rect.ATTRIBUTE_NAMES=e.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),e.Rect.fromElement=function(t,r){if(!t)return null;r=r||{};var n=e.parseAttributes(t,e.Rect.ATTRIBUTE_NAMES);n.left=n.left||0,n.top=n.top||0;var s=new e.Rect(i(r?e.util.object.clone(r):{},n));return s.visible=s.visible&&s.width>0&&s.height>0,s},e.Rect.fromObject=function(t,i,r){return e.Object._fromObject("Rect",t,i,r)}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={});if(e.Polyline)return void e.warn("fabric.Polyline is already defined");var i=e.Object.prototype.cacheProperties.concat();i.push("points"),e.Polyline=e.util.createClass(e.Object,{type:"polyline",points:null,minX:0,minY:0,cacheProperties:i,initialize:function(t,i){return e.Polygon.prototype.initialize.call(this,t,i)},_calcDimensions:function(){return e.Polygon.prototype._calcDimensions.call(this)},toObject:function(t){return e.Polygon.prototype.toObject.call(this,t)},toSVG:function(t){return e.Polygon.prototype.toSVG.call(this,t)},_render:function(t,i){e.Polygon.prototype.commonRender.call(this,t,i)&&(this._renderFill(t),this._renderStroke(t))},_renderDashedStroke:function(t){var i,r;t.beginPath();for(var n=0,s=this.points.length;n\n'),t?t(r.join("")):r.join("")},_render:function(t,e){this.commonRender(t,e)&&(this._renderFill(t),(this.stroke||this.strokeDashArray)&&(t.closePath(),this._renderStroke(t)))},commonRender:function(t,e){var i,r=this.points.length,n=e?0:this.pathOffset.x,s=e?0:this.pathOffset.y;if(!r||isNaN(this.points[r-1].y))return!1;t.beginPath(),t.moveTo(this.points[0].x-n,this.points[0].y-s);for(var o=0;o"},toObject:function(t){var e=n(this.callSuper("toObject",["sourcePath","pathOffset"].concat(t)),{ -path:this.path.map(function(t){return t.slice()})});return e},toDatalessObject:function(t){var e=this.toObject(t);return this.sourcePath&&(e.path=this.sourcePath),delete e.sourcePath,e},toSVG:function(t){for(var e=[],i=this._createBaseSVGMarkup(),r="",n=0,s=this.path.length;n\n"),t?t(i.join("")):i.join("")},complexity:function(){return this.path.length},_parsePath:function(){for(var t,e,i,r,n,s=[],o=[],c=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,l=0,u=this.path.length;lp)for(var b=1,m=n.length;b\n");for(var s=0,o=e.length;s\n"),t?t(n.join("")):n.join("")},toString:function(){return"#"},isSameColor:function(){var t=this.getObjects()[0].get("fill")||"";return"string"==typeof t&&(t=t.toLowerCase(),this.getObjects().every(function(e){var i=e.get("fill")||"";return"string"==typeof i&&i.toLowerCase()===t}))},complexity:function(){return this.paths.reduce(function(t,e){return t+(e&&e.complexity?e.complexity():0)},0)},getObjects:function(){return this.paths}}),e.PathGroup.fromObject=function(t,i){"string"==typeof t.paths?e.loadSVGFromURL(t.paths,function(r){var n=t.paths;delete t.paths;var s=e.util.groupSVGElements(r,t,n);i(s)}):e.util.enlivenObjects(t.paths,function(r){delete t.paths,i(new e.PathGroup(r,t))})},void(e.PathGroup.async=!0))}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.util.array.min,n=e.util.array.max;if(!e.Group){var s={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};e.Group=e.util.createClass(e.Object,e.Collection,{type:"group",strokeWidth:0,subTargetCheck:!1,initialize:function(t,e,i){e=e||{},this._objects=[],i&&this.callSuper("initialize",e),this._objects=t||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;this.originalState={},e.originX&&(this.originX=e.originX),e.originY&&(this.originY=e.originY),i?this._updateObjectsCoords(!0):(this._calcBounds(),this._updateObjectsCoords(),this.callSuper("initialize",e)),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(t){for(var e=this.getCenterPoint(),i=this._objects.length;i--;)this._updateObjectCoords(this._objects[i],e,t)},_updateObjectCoords:function(t,e,i){if(t.__origHasControls=t.hasControls,t.hasControls=!1,!i){var r=t.getLeft(),n=t.getTop(),s=!0;t.set({originalLeft:r,originalTop:n,left:r-e.x,top:n-e.y}),t.setCoords(s)}},toString:function(){return"#"},addWithUpdate:function(t){return this._restoreObjectsState(),e.util.resetObjectTransform(this),t&&(this._objects.push(t),t.group=this,t._set("canvas",this.canvas)),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this.dirty=!0,this},_setObjectActive:function(t){t.set("active",!0),t.group=this},removeWithUpdate:function(t){return this._restoreObjectsState(),e.util.resetObjectTransform(this),this.forEachObject(this._setObjectActive,this),this.remove(t),this._calcBounds(),this._updateObjectsCoords(),this.dirty=!0,this},_onObjectAdded:function(t){this.dirty=!0,t.group=this,t._set("canvas",this.canvas)},_onObjectRemoved:function(t){this.dirty=!0,delete t.group,t.set("active",!1)},delegatedProperties:{fill:!0,stroke:!0,strokeWidth:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(t,e){var i=this._objects.length;if(this.delegatedProperties[t]||"canvas"===t)for(;i--;)this._objects[i].set(t,e);else for(;i--;)this._objects[i].setOnGroup(t,e);this.callSuper("_set",t,e)},toObject:function(t){var e=this.getObjects().map(function(e){var i=e.includeDefaultValues;e.includeDefaultValues=e.group.includeDefaultValues;var r=e.toObject(t);return e.includeDefaultValues=i,r});return i(this.callSuper("toObject",t),{objects:e})},render:function(t){this._transformDone=!0,this.callSuper("render",t),this._transformDone=!1},drawObject:function(t){for(var e=0,i=this._objects.length;e\n');for(var i=0,r=this._objects.length;i\n"),t?t(e.join("")):e.join("")},get:function(t){if(t in s){if(this[t])return this[t];for(var e=0,i=this._objects.length;e\n',"\n"),this.stroke||this.strokeDashArray){var o=this.fill;this.fill=null,e.push("\n'),this.fill=o}return e.push("\n"),t?t(e.join("")):e.join("")},getSrc:function(t){var e=t?this._element:this._originalElement;return e?fabric.isLikelyNode?e._src:e.src:this.src||""},setSrc:function(t,e,i){fabric.util.loadImage(t,function(t){return this.setElement(t,e,i)},this,i&&i.crossOrigin)},toString:function(){return'#'},applyFilters:function(t,e,i,r){if(e=e||this.filters,i=i||this._originalElement){var n,s,o=fabric.util.createImage(),a=this.canvas?this.canvas.getRetinaScaling():fabric.devicePixelRatio,h=this.minimumScaleTrigger/a,c=this;if(0===e.length)return this._element=i,t&&t(this),i;var l=fabric.util.createCanvasElement();return l.width=i.width,l.height=i.height,l.getContext("2d").drawImage(i,0,0,i.width,i.height),e.forEach(function(t){t&&(r?(n=c.scaleX0?90*Math.round((t-1)/90):90*Math.round(t/90)},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(t){t=t||{};var e=function(){},i=t.onComplete||e,r=t.onChange||e,n=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(t){n.setAngle(t),r()},onComplete:function(){n.setCoords(),i()},onStart:function(){n.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(t){return t.straighten(),this.renderAll(),this},fxStraightenObject:function(t){return t.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",initialize:function(t){t&&this.setOptions(t)},setOptions:function(t){for(var e in t)this[e]=t[e]},toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),fabric.Image.filters.BaseFilter.fromObject=function(t,e){var i=new fabric.Image.filters[t.type](t);return e&&e(i),i},function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.Image.filters,n=e.util.createClass;r.Brightness=n(r.BaseFilter,{type:"Brightness",initialize:function(t){t=t||{},this.brightness=t.brightness||0},applyTo:function(t){for(var e=t.getContext("2d"),i=e.getImageData(0,0,t.width,t.height),r=i.data,n=this.brightness,s=0,o=r.length;sb||o<0||o>v||(h=4*(a*v+o),c=l[S*d+w],e+=p[h]*c,i+=p[h+1]*c,r+=p[h+2]*c,n+=p[h+3]*c);y[s]=e,y[s+1]=i,y[s+2]=r,y[s+3]=n+_*(255-n)}u.putImageData(m,0,0)},toObject:function(){return i(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),e.Image.filters.Convolute.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.Image.filters,n=e.util.createClass;r.GradientTransparency=n(r.BaseFilter,{type:"GradientTransparency",initialize:function(t){t=t||{},this.threshold=t.threshold||100},applyTo:function(t){for(var e=t.getContext("2d"),i=e.getImageData(0,0,t.width,t.height),r=i.data,n=this.threshold,s=r.length,o=0,a=r.length;o-1?t.channel:0},applyTo:function(t){if(this.mask){var i,r=t.getContext("2d"),n=r.getImageData(0,0,t.width,t.height),s=n.data,o=this.mask.getElement(),a=e.util.createCanvasElement(),h=this.channel,c=n.width*n.height*4;a.width=t.width,a.height=t.height,a.getContext("2d").drawImage(o,0,0,t.width,t.height);var l=a.getContext("2d").getImageData(0,0,t.width,t.height),u=l.data;for(i=0;ic&&i>c&&r>c&&l(e-i)i&&(l=2,f=-1),a>n&&(u=2,d=-1),h=c.getImageData(0,0,i,n),t.width=o(s,i),t.height=o(a,n),c.putImageData(h,0,0);!g||!p;)i=v,n=b,s*ft)return 0;if(e*=Math.PI,s(e)<1e-16)return 1;var i=e/t;return h(e)*h(i)/e/i}}function f(t){var h,c,u,d,g,k,P,M,A,D,I;for(T.x=(t+.5)*y,j.x=r(T.x),h=0;h=e)){D=r(1e3*s(c-T.x)),O[D]||(O[D]={});for(var E=j.y-w;E<=j.y+w;E++)E<0||E>=o||(I=r(1e3*s(E-T.y)),O[D][I]||(O[D][I]=m(n(i(D*x,2)+i(I*C,2))/1e3)),u=O[D][I],u>0&&(d=4*(E*e+c),g+=u,k+=u*v[d],P+=u*v[d+1],M+=u*v[d+2],A+=u*v[d+3]))}d=4*(h*a+t),b[d]=k/g,b[d+1]=P/g,b[d+2]=M/g,b[d+3]=A/g}return++t1&&L<-1||(x=2*L*L*L-3*L*L+1,x>0&&(E=4*(I+P*e),j+=x*p[E+3],S+=x,p[E+3]<255&&(x=x*p[E+3]/250),w+=x*p[E],O+=x*p[E+1],T+=x*p[E+2],C+=x))}b[_]=w/C,b[_+1]=O/C,b[_+2]=T/C,b[_+3]=j/S}return v},toObject:function(){return{type:this.type,scaleX:this.scaleX,scaleY:this.scaleY,resizeType:this.resizeType,lanczosLobes:this.lanczosLobes}}}),e.Image.filters.Resize.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.Image.filters,n=e.util.createClass;r.ColorMatrix=n(r.BaseFilter,{type:"ColorMatrix",initialize:function(t){t||(t={}),this.matrix=t.matrix||[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0]},applyTo:function(t){var e,i,r,n,s,o=t.getContext("2d"),a=o.getImageData(0,0,t.width,t.height),h=a.data,c=h.length,l=this.matrix;for(e=0;e'},_getCacheCanvasDimensions:function(){var t=this.callSuper("_getCacheCanvasDimensions"),e=2*Math.ceil(this.fontSize);return t.width+=e,t.height+=e,t},_render:function(t){this._setTextStyles(t),this.group&&"path-group"===this.group.type&&t.translate(this.left,this.top),this._renderTextLinesBackground(t),this._renderText(t),this._renderTextDecoration(t)},_renderText:function(t){this._renderTextFill(t),this._renderTextStroke(t)},_setTextStyles:function(t){t.textBaseline="alphabetic",t.font=this._getFontDeclaration()},_getTextHeight:function(){return this._getHeightOfSingleLine()+(this._textLines.length-1)*this._getHeightOfLine()},_getTextWidth:function(t){for(var e=this._getLineWidth(t,0),i=1,r=this._textLines.length;ie&&(e=n)}return e},_renderChars:function(t,e,i,r,n){var s,o,a=t.slice(0,-4);if(this[a].toLive){var h=-this.width/2+this[a].offsetX||0,c=-this.height/2+this[a].offsetY||0;e.save(),e.translate(h,c),r-=h,n-=c}if(0!==this.charSpacing){var l=this._getWidthOfCharSpacing();i=i.split("");for(var u=0,f=i.length;u0?o:0}else e[t](i,r,n);this[a].toLive&&e.restore()},_renderTextLine:function(t,e,i,r,n,s){n-=this.fontSize*this._fontSizeFraction;var o=this._getLineWidth(e,s);if("justify"!==this.textAlign||this.width0?u/f:0,g=0,p=0,v=h.length;p0?n:0},_getLeftOffset:function(){return-this.width/2},_getTopOffset:function(){return-this.height/2},isEmptyStyles:function(){return!0},_renderTextCommon:function(t,e){for(var i=0,r=this._getLeftOffset(),n=this._getTopOffset(),s=0,o=this._textLines.length;s0&&(r=this._getLineLeftOffset(i),t.fillRect(this._getLeftOffset()+r,this._getTopOffset()+n,i,e/this.lineHeight)),n+=e;t.fillStyle=s,this._removeShadow(t)}},_getLineLeftOffset:function(t){return"center"===this.textAlign?(this.width-t)/2:"right"===this.textAlign?this.width-t:0},_clearCache:function(){this.__lineWidths=[],this.__lineHeights=[]},_shouldClearDimensionCache:function(){var t=this._forceClearCache;return t||(t=this.hasStateChanged("_dimensionAffectingProps")),t&&(this.saveState({propertySet:"_dimensionAffectingProps"}),this.dirty=!0),t},_getLineWidth:function(t,e){if(this.__lineWidths[e])return this.__lineWidths[e]===-1?this.width:this.__lineWidths[e];var i,r,n=this._textLines[e];return i=""===n?0:this._measureLine(t,e),this.__lineWidths[e]=i,i&&"justify"===this.textAlign&&(r=n.split(/\s+/),r.length>1&&(this.__lineWidths[e]=-1)),i},_getWidthOfCharSpacing:function(){return 0!==this.charSpacing?this.fontSize*this.charSpacing/1e3:0},_measureLine:function(t,e){var i,r,n=this._textLines[e],s=t.measureText(n).width,o=0;return 0!==this.charSpacing&&(i=n.split("").length,o=(i-1)*this._getWidthOfCharSpacing()),r=s+o,r>0?r:0},_renderTextDecoration:function(t){function e(e){var n,s,o,a,h,c,l,u=0;for(n=0,s=r._textLines.length;n-1&&n.push(.85),this.textDecoration.indexOf("line-through")>-1&&n.push(.43),this.textDecoration.indexOf("overline")>-1&&n.push(-.12),n.length>0&&e(n)}},_getFontDeclaration:function(){return[e.isLikelyNode?this.fontWeight:this.fontStyle,e.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",e.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(t,e){this.visible&&(this._shouldClearDimensionCache()&&(this._setTextStyles(t),this._initDimensions(t)),this.callSuper("render",t,e))},_splitTextIntoLines:function(){return this.text.split(this._reNewline)},toObject:function(t){var e=["text","fontSize","fontWeight","fontFamily","fontStyle","lineHeight","textDecoration","textAlign","textBackgroundColor","charSpacing"].concat(t);return this.callSuper("toObject",e)},toSVG:function(t){this.ctx||(this.ctx=e.util.createCanvasElement().getContext("2d"));var i=this._createBaseSVGMarkup(),r=this._getSVGLeftTopOffsets(this.ctx),n=this._getSVGTextAndBg(r.textTop,r.textLeft);return this._wrapSVGTextAndBg(i,n),t?t(i.join("")):i.join("")},_getSVGLeftTopOffsets:function(t){var e=this._getHeightOfLine(t,0),i=-this.width/2,r=0;return{textLeft:i+(this.group&&"path-group"===this.group.type?this.left:0),textTop:r+(this.group&&"path-group"===this.group.type?-this.top:0),lineTop:e}},_wrapSVGTextAndBg:function(t,e){var i=!0,r=this.getSvgFilter(),n=""===r?"":' style="'+r+'"';t.push("\t\n",e.textBgRects.join(""),"\t\t\n',e.textSpans.join(""),"\t\t\n","\t\n")},_getSVGTextAndBg:function(t,e){var i=[],r=[],n=0;this._setSVGBg(r);for(var s=0,o=this._textLines.length;s",e.util.string.escapeXml(this._textLines[t]),"\n")},_setSVGTextLineJustifed:function(t,n,s,o){var a=e.util.createCanvasElement().getContext("2d");this._setTextStyles(a);var h,c,l=this._textLines[t],u=l.split(/\s+/),f=this._getWidthOfWords(a,u.join("")),d=this.width-f,g=u.length-1,p=g>0?d/g:0,v=this._getFillAttributes(this.fill);for(o+=this._getLineLeftOffset(this._getLineWidth(a,t)),t=0,c=u.length;t",e.util.string.escapeXml(h),"\n"),o+=this._getWidthOfWords(a,h)+p},_setSVGTextLineBg:function(t,e,n,s,o){t.push("\t\t\n')},_setSVGBg:function(t){this.backgroundColor&&t.push("\t\t\n')},_getFillAttributes:function(t){var i=t&&"string"==typeof t?new e.Color(t):"";return i&&i.getSource()&&1!==i.getAlpha()?'opacity="'+i.getAlpha()+'" fill="'+i.setAlpha(1).toRgb()+'"':'fill="'+t+'"'},_set:function(t,e){this.callSuper("_set",t,e),this._dimensionAffectingProps.indexOf(t)>-1&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),e.Text.ATTRIBUTE_NAMES=e.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),e.Text.DEFAULT_SVG_FONT_SIZE=16,e.Text.fromElement=function(t,i){if(!t)return null;var r=e.parseAttributes(t,e.Text.ATTRIBUTE_NAMES);i=e.util.object.extend(i?e.util.object.clone(i):{},r),i.top=i.top||0,i.left=i.left||0,"dx"in r&&(i.left+=r.dx),"dy"in r&&(i.top+=r.dy),"fontSize"in i||(i.fontSize=e.Text.DEFAULT_SVG_FONT_SIZE),i.originX||(i.originX="left");var n="";"textContent"in t?n=t.textContent:"firstChild"in t&&null!==t.firstChild&&"data"in t.firstChild&&null!==t.firstChild.data&&(n=t.firstChild.data),n=n.replace(/^\s+|\s+$|\n+/g,"").replace(/\s+/g," ");var s=new e.Text(n,i),o=s.getHeight()/s.height,a=(s.height+s.strokeWidth)*s.lineHeight-s.height,h=a*o,c=s.getHeight()+h,l=0;return"left"===s.originX&&(l=s.getWidth()/2),"right"===s.originX&&(l=-s.getWidth()/2),s.set({left:s.getLeft()+l,top:s.getTop()-c/2+s.fontSize*(.18+s._fontSizeFraction)/s.lineHeight}),s},e.Text.fromObject=function(t,i,r){return e.Object._fromObject("Text",t,i,r,"text")},e.util.createAccessors(e.Text)}("undefined"!=typeof exports?exports:this),function(){var t=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_reSpace:/\s|\n/,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,__widthOfSpace:[],initialize:function(t,e){this.styles=e?e.styles||{}:{},this.callSuper("initialize",t,e),this.initBehavior()},_clearCache:function(){this.callSuper("_clearCache"),this.__widthOfSpace=[]},isEmptyStyles:function(){if(!this.styles)return!0;var t=this.styles;for(var e in t)for(var i in t[e])for(var r in t[e][i])return!1;return!0},setSelectionStart:function(t){t=Math.max(t,0),this._updateAndFire("selectionStart",t)},setSelectionEnd:function(t){t=Math.min(t,this.text.length),this._updateAndFire("selectionEnd",t)},_updateAndFire:function(t,e){this[t]!==e&&(this._fireSelectionChanged(),this[t]=e),this._updateTextarea()},_fireSelectionChanged:function(){this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})},getSelectionStyles:function(t,e){if(2===arguments.length){for(var i=[],r=t;r0?a:0,lineLeft:r},this.cursorOffsetCache=i,this.cursorOffsetCache},renderCursor:function(t,e){var i=this.get2DCursorLocation(),r=i.lineIndex,n=i.charIndex,s=this.getCurrentCharFontSize(r,n),o=0===r&&0===n?this._getLineLeftOffset(this._getLineWidth(e,r)):t.leftOffset,a=this.scaleX*this.canvas.getZoom(),h=this.cursorWidth/a;e.fillStyle=this.getCurrentCharColor(r,n),e.globalAlpha=this.__isMousedown?1:this._currentCursorOpacity,e.fillRect(t.left+o-h/2,t.top+t.topOffset,h,s)},renderSelection:function(t,e,i){i.fillStyle=this.selectionColor;for(var r=this.get2DCursorLocation(this.selectionStart),n=this.get2DCursorLocation(this.selectionEnd),s=r.lineIndex,o=n.lineIndex,a=s;a<=o;a++){var h=this._getLineLeftOffset(this._getLineWidth(i,a))||0,c=this._getHeightOfLine(this.ctx,a),l=0,u=0,f=this._textLines[a];if(a===s){for(var d=0,g=f.length;d=r.charIndex&&(a!==o||ds&&a1)&&(c/=this.lineHeight),i.fillRect(e.left+h,e.top+e.topOffset,u>0?u:0,c),e.topOffset+=l}},_renderChars:function(t,e,i,r,n,s,o){if(this.isEmptyStyles())return this._renderCharsFast(t,e,i,r,n);o=o||0;var a,h,c=this._getHeightOfLine(e,s),l="";e.save(),n-=c/this.lineHeight*this._fontSizeFraction;for(var u=o,f=i.length+o;u<=f;u++)a=a||this.getCurrentCharStyle(s,u),h=this.getCurrentCharStyle(s,u+1),(this._hasStyleChanged(a,h)||u===f)&&(this._renderChar(t,e,s,u-1,l,r,n,c),l="",a=h),l+=i[u-o];e.restore()},_renderCharsFast:function(t,e,i,r,n){"fillText"===t&&this.fill&&this.callSuper("_renderChars",t,e,i,r,n),"strokeText"===t&&(this.stroke&&this.strokeWidth>0||this.skipFillStrokeCheck)&&this.callSuper("_renderChars",t,e,i,r,n)},_renderChar:function(t,e,i,r,n,s,o,a){var h,c,l,u,f,d,g,p,v,b=this._getStyleDeclaration(i,r);if(b?(c=this._getHeightOfChar(e,n,i,r),u=b.stroke,l=b.fill,d=b.textDecoration):c=this.fontSize,u=(u||this.stroke)&&"strokeText"===t,l=(l||this.fill)&&"fillText"===t,b&&e.save(),h=this._applyCharStylesGetWidth(e,n,i,r,b||null),d=d||this.textDecoration,b&&b.textBackgroundColor&&this._removeShadow(e),0!==this.charSpacing){p=this._getWidthOfCharSpacing(),g=n.split(""),h=0;for(var m,y=0,_=g.length;y<_;y++)m=g[y],l&&e.fillText(m,s+h,o),u&&e.strokeText(m,s+h,o),v=e.measureText(m).width+p,h+=v>0?v:0}else l&&e.fillText(n,s,o),u&&e.strokeText(n,s,o);(d||""!==d)&&(f=this._fontSizeFraction*a/this.lineHeight,this._renderCharDecoration(e,d,s,o,f,h,c)),b&&e.restore(),e.translate(h,0)},_hasStyleChanged:function(t,e){return t.fill!==e.fill||t.fontSize!==e.fontSize||t.textBackgroundColor!==e.textBackgroundColor||t.textDecoration!==e.textDecoration||t.fontFamily!==e.fontFamily||t.fontWeight!==e.fontWeight||t.fontStyle!==e.fontStyle||t.stroke!==e.stroke||t.strokeWidth!==e.strokeWidth},_renderCharDecoration:function(t,e,i,r,n,s,o){if(e){var a,h,c=o/15,l={underline:r+o/10,"line-through":r-o*(this._fontSizeFraction+this._fontSizeMult-1)+c,overline:r-(this._fontSizeMult-this._fontSizeFraction)*o},u=["underline","line-through","overline"];for(a=0;a-1&&t.fillRect(i,l[h],s,c)}},_renderTextLine:function(t,e,i,r,n,s){this.isEmptyStyles()||(n+=this.fontSize*(this._fontSizeFraction+.03)),this.callSuper("_renderTextLine",t,e,i,r,n,s)},_renderTextDecoration:function(t){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",t)},_renderTextLinesBackground:function(t){this.callSuper("_renderTextLinesBackground",t);var e,i,r,n,s,o,a=0,h=this._getLeftOffset(),c=this._getTopOffset();t.save();for(var l=0,u=this._textLines.length;l0?n:0},_getHeightOfChar:function(t,e,i){var r=this._getStyleDeclaration(e,i);return r&&r.fontSize?r.fontSize:this.fontSize},_getWidthOfCharsAt:function(t,e,i){var r,n,s=0;for(r=0;r0?i:0},_getWidthOfSpace:function(t,e){if(this.__widthOfSpace[e])return this.__widthOfSpace[e];var i=this._textLines[e],r=this._getWidthOfWords(t,i,e,0),n=this.width-r,s=i.length-i.replace(this._reSpacesAndTabs,"").length,o=Math.max(n/s,t.measureText(" ").width);return this.__widthOfSpace[e]=o,o},_getWidthOfWords:function(t,e,i,r){for(var n=0,s=0;sr&&(r=o)}return this.__lineHeights[e]=r*this.lineHeight*this._fontSizeMult,this.__lineHeights[e]},_getTextHeight:function(t){for(var e,i=0,r=0,n=this._textLines.length;r-1;)e++,i--;return t-e},findWordBoundaryRight:function(t){var e=0,i=t;if(this._reSpace.test(this.text.charAt(i)))for(;this._reSpace.test(this.text.charAt(i));)e++,i++;for(;/\S/.test(this.text.charAt(i))&&i-1;)e++,i--;return t-e},findLineBoundaryRight:function(t){for(var e=0,i=t;!/\n/.test(this.text.charAt(i))&&i0&&ithis.__selectionStartOnMouseDown?(this.selectionStart=this.__selectionStartOnMouseDown,this.selectionEnd=e):(this.selectionStart=e,this.selectionEnd=this.__selectionStartOnMouseDown),this.selectionStart===i&&this.selectionEnd===r||(this._fireSelectionChanged(),this._updateTextarea(),this.renderCursorOrSelection()))}},_setEditingProps:function(){this.hoverCursor="text",this.canvas&&(this.canvas.defaultCursor=this.canvas.moveCursor="text"),this.borderColor=this.editingBorderColor,this.hasControls=this.selectable=!1,this.lockMovementX=this.lockMovementY=!0},_updateTextarea:function(){if(this.hiddenTextarea&&!this.inCompositionMode&&(this.cursorOffsetCache={},this.hiddenTextarea.value=this.text,this.hiddenTextarea.selectionStart=this.selectionStart,this.hiddenTextarea.selectionEnd=this.selectionEnd,this.selectionStart===this.selectionEnd)){var t=this._calcTextareaPosition();this.hiddenTextarea.style.left=t.left,this.hiddenTextarea.style.top=t.top,this.hiddenTextarea.style.fontSize=t.fontSize}},_calcTextareaPosition:function(){if(!this.canvas)return{x:1,y:1};var t=this.text.split(""),e=this._getCursorBoundaries(t,"cursor"),i=this.get2DCursorLocation(),r=i.lineIndex,n=i.charIndex,s=this.getCurrentCharFontSize(r,n),o=0===r&&0===n?this._getLineLeftOffset(this._getLineWidth(this.ctx,r)):e.leftOffset,a=this.calcTransformMatrix(),h={x:e.left+o,y:e.top+e.topOffset+s},c=this.canvas.upperCanvasEl,l=c.width-s,u=c.height-s;return h=fabric.util.transformPoint(h,a),h=fabric.util.transformPoint(h,this.canvas.viewportTransform),h.x<0&&(h.x=0),h.x>l&&(h.x=l),h.y<0&&(h.y=0),h.y>u&&(h.y=u),h.x+=this.canvas._offset.left,h.y+=this.canvas._offset.top,{left:h.x+"px",top:h.y+"px",fontSize:s}},_saveEditingProps:function(){this._savedProps={hasControls:this.hasControls,borderColor:this.borderColor,lockMovementX:this.lockMovementX,lockMovementY:this.lockMovementY,hoverCursor:this.hoverCursor,defaultCursor:this.canvas&&this.canvas.defaultCursor,moveCursor:this.canvas&&this.canvas.moveCursor}},_restoreEditingProps:function(){this._savedProps&&(this.hoverCursor=this._savedProps.overCursor,this.hasControls=this._savedProps.hasControls,this.borderColor=this._savedProps.borderColor,this.lockMovementX=this._savedProps.lockMovementX,this.lockMovementY=this._savedProps.lockMovementY,this.canvas&&(this.canvas.defaultCursor=this._savedProps.defaultCursor,this.canvas.moveCursor=this._savedProps.moveCursor))},exitEditing:function(){var t=this._textBeforeEdit!==this.text;return this.selected=!1,this.isEditing=!1,this.selectable=!0,this.selectionEnd=this.selectionStart,this.hiddenTextarea.blur&&this.hiddenTextarea.blur(),this.hiddenTextarea&&this.canvas&&this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea),this.hiddenTextarea=null,this.abortCursorAnimation(),this._restoreEditingProps(),this._currentCursorOpacity=0,this.fire("editing:exited"),t&&this.fire("modified"),this.canvas&&(this.canvas.off("mouse:move",this.mouseMoveHandler),this.canvas.fire("text:editing:exited",{target:this}),t&&this.canvas.fire("object:modified",{target:this})),this},_removeExtraneousStyles:function(){for(var t in this.styles)this._textLines[t]||delete this.styles[t]},_removeCharsFromTo:function(t,e){for(;e!==t;)this._removeSingleCharAndStyle(t+1),e--;this.selectionStart=t,this.selectionEnd=t},_removeSingleCharAndStyle:function(t){var e="\n"===this.text[t-1],i=e?t:t-1;this.removeStyleObject(e,i),this.text=this.text.slice(0,t-1)+this.text.slice(t),this._textLines=this._splitTextIntoLines()},insertChars:function(t,e){var i;if(this.selectionEnd-this.selectionStart>1&&this._removeCharsFromTo(this.selectionStart,this.selectionEnd),!e&&this.isEmptyStyles())return void this.insertChar(t,!1);for(var r=0,n=t.length;r=i&&(s[parseInt(o,10)-i]=this.styles[e][o],delete this.styles[e][o]);this.styles[e+1]=s}this._forceClearCache=!0},insertCharStyleObject:function(e,i,r){var n=this.styles[e],s=t(n);0!==i||r||(i=1);for(var o in s){var a=parseInt(o,10);a>=i&&(n[a+1]=s[a],s[a-1]||delete n[a])}this.styles[e][i]=r||t(n[i-1]),this._forceClearCache=!0},insertStyleObjects:function(t,e,i){var r=this.get2DCursorLocation(),n=r.lineIndex,s=r.charIndex;this._getLineStyle(n)||this._setLineStyle(n,{}),"\n"===t?this.insertNewlineStyleObject(n,s,e):this.insertCharStyleObject(n,s,i)},shiftLineStyles:function(e,i){var r=t(this.styles);for(var n in this.styles){var s=parseInt(n,10);s>e&&(this.styles[s+i]=r[s],r[s-i]||delete this.styles[s])}},removeStyleObject:function(t,e){var i=this.get2DCursorLocation(e),r=i.lineIndex,n=i.charIndex;this._removeStyleObject(t,i,r,n)},_getTextOnPreviousLine:function(t){return this._textLines[t-1]},_removeStyleObject:function(e,i,r,n){if(e){var s=this._getTextOnPreviousLine(i.lineIndex),o=s?s.length:0;this.styles[r-1]||(this.styles[r-1]={});for(n in this.styles[r])this.styles[r-1][parseInt(n,10)+o]=this.styles[r][n];this.shiftLineStyles(i.lineIndex,-1)}else{var a=this.styles[r];a&&delete a[n];var h=t(a);for(var c in h){var l=parseInt(c,10);l>=n&&0!==l&&(a[l-1]=h[l],delete a[l])}}},insertNewline:function(){this.insertChars("\n")},setSelectionStartEndWithShift:function(t,e,i){i<=t?(e===t?this._selectionDirection="left":"right"===this._selectionDirection&&(this._selectionDirection="left",this.selectionEnd=t),this.selectionStart=i):i>t&&it?this.selectionStart=t:this.selectionStart<0&&(this.selectionStart=0),this.selectionEnd>t?this.selectionEnd=t:this.selectionEnd<0&&(this.selectionEnd=0)}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date,this.__lastLastClickTime=+new Date,this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(t){this.__newClickTime=+new Date;var e=this.canvas.getPointer(t.e);this.isTripleClick(e)?(this.fire("tripleclick",t),this._stopEvent(t.e)):this.isDoubleClick(e)&&(this.fire("dblclick",t),this._stopEvent(t.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=e,this.__lastIsEditing=this.isEditing,this.__lastSelected=this.selected},isDoubleClick:function(t){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===t.x&&this.__lastPointer.y===t.y&&this.__lastIsEditing},isTripleClick:function(t){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===t.x&&this.__lastPointer.y===t.y},_stopEvent:function(t){t.preventDefault&&t.preventDefault(),t.stopPropagation&&t.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(t){this.selectWord(this.getSelectionStartFromPointer(t.e))}),this.on("tripleclick",function(t){this.selectLine(this.getSelectionStartFromPointer(t.e))})},initMousedownHandler:function(){this.on("mousedown",function(t){if(this.editable){var e=this.canvas.getPointer(t.e);this.__mousedownX=e.x,this.__mousedownY=e.y,this.__isMousedown=!0,this.selected&&this.setCursorByClick(t.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.selectionStart===this.selectionEnd&&this.abortCursorAnimation(),this.renderCursorOrSelection())}})},_isObjectMoved:function(t){var e=this.canvas.getPointer(t);return this.__mousedownX!==e.x||this.__mousedownY!==e.y},initMouseupHandler:function(){this.on("mouseup",function(t){this.__isMousedown=!1,this.editable&&!this._isObjectMoved(t.e)&&(this.__lastSelected&&!this.__corner&&(this.enterEditing(t.e),this.selectionStart===this.selectionEnd?this.initDelayedCursor(!0):this.renderCursorOrSelection()),this.selected=!0)})},setCursorByClick:function(t){var e=this.getSelectionStartFromPointer(t),i=this.selectionStart,r=this.selectionEnd;t.shiftKey?this.setSelectionStartEndWithShift(i,r,e):(this.selectionStart=e,this.selectionEnd=e),this._fireSelectionChanged(),this._updateTextarea()},getSelectionStartFromPointer:function(t){for(var e,i,r=this.getLocalPointer(t),n=0,s=0,o=0,a=0,h=0,c=this._textLines.length;hs?0:1,h=r+a;return this.flipX&&(h=n-h),h>this.text.length&&(h=this.text.length),h}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off");var t=this._calcTextareaPosition();this.hiddenTextarea.style.cssText="position: absolute; top: "+t.top+"; left: "+t.left+"; opacity: 0; width: 0px; height: 0px; z-index: -999;",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keyup",this.onKeyUp.bind(this)),fabric.util.addListener(this.hiddenTextarea,"input",this.onInput.bind(this)),fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"cut",this.cut.bind(this)),fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionstart",this.onCompositionStart.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionupdate",this.onCompositionUpdate.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionend",this.onCompositionEnd.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",9:"exitEditing",27:"exitEditing",13:"insertNewline",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMapUp:{67:"copy",88:"cut"},_ctrlKeysMapDown:{65:"selectAll"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(t){if(this.isEditing){if(t.keyCode in this._keysMap)this[this._keysMap[t.keyCode]](t);else{if(!(t.keyCode in this._ctrlKeysMapDown&&(t.ctrlKey||t.metaKey)))return;this[this._ctrlKeysMapDown[t.keyCode]](t)}t.stopImmediatePropagation(),t.preventDefault(),t.keyCode>=33&&t.keyCode<=40?(this.clearContextTop(),this.renderCursorOrSelection()):this.canvas&&this.canvas.renderAll()}},onKeyUp:function(t){return!this.isEditing||this._copyDone?void(this._copyDone=!1):void(t.keyCode in this._ctrlKeysMapUp&&(t.ctrlKey||t.metaKey)&&(this[this._ctrlKeysMapUp[t.keyCode]](t),t.stopImmediatePropagation(),t.preventDefault(),this.canvas&&this.canvas.renderAll()))},onInput:function(t){if(this.isEditing&&!this.inCompositionMode){var e,i,r,n=this.selectionStart||0,s=this.selectionEnd||0,o=this.text.length,a=this.hiddenTextarea.value.length;a>o?(r="left"===this._selectionDirection?s:n,e=a-o,i=this.hiddenTextarea.value.slice(r,r+e)):(e=a-o+s-n,i=this.hiddenTextarea.value.slice(n,n+e)),this.insertChars(i),t.stopPropagation()}},onCompositionStart:function(){this.inCompositionMode=!0,this.prevCompositionLength=0,this.compositionStart=this.selectionStart},onCompositionEnd:function(){this.inCompositionMode=!1},onCompositionUpdate:function(t){var e=t.data;this.selectionStart=this.compositionStart,this.selectionEnd=this.selectionEnd===this.selectionStart?this.compositionStart+this.prevCompositionLength:this.selectionEnd,this.insertChars(e,!1),this.prevCompositionLength=e.length},forwardDelete:function(t){if(this.selectionStart===this.selectionEnd){if(this.selectionStart===this.text.length)return;this.moveCursorRight(t)}this.removeChars(t)},copy:function(t){if(this.selectionStart!==this.selectionEnd){var e=this.getSelectedText(),i=this._getClipboardData(t);i&&i.setData("text",e),fabric.copiedText=e,fabric.copiedTextStyle=this.getSelectionStyles(this.selectionStart,this.selectionEnd),t.stopImmediatePropagation(),t.preventDefault(),this._copyDone=!0}},paste:function(t){var e=null,i=this._getClipboardData(t),r=!0;i?(e=i.getData("text").replace(/\r/g,""),fabric.copiedTextStyle&&fabric.copiedText===e||(r=!1)):e=fabric.copiedText,e&&this.insertChars(e,r),t.stopImmediatePropagation(),t.preventDefault()},cut:function(t){this.selectionStart!==this.selectionEnd&&(this.copy(t),this.removeChars(t))},_getClipboardData:function(t){return t&&t.clipboardData||fabric.window.clipboardData},_getWidthBeforeCursor:function(t,e){for(var i,r=this._textLines[t].slice(0,e),n=this._getLineWidth(this.ctx,t),s=this._getLineLeftOffset(n),o=0,a=r.length;oe){i=!0;var f=o-u,d=o,g=Math.abs(f-e),p=Math.abs(d-e);a=p=this.text.length&&this.selectionEnd>=this.text.length||this._moveCursorUpOrDown("Down",t)},moveCursorUp:function(t){0===this.selectionStart&&0===this.selectionEnd||this._moveCursorUpOrDown("Up",t)},_moveCursorUpOrDown:function(t,e){var i="get"+t+"CursorOffset",r=this[i](e,"right"===this._selectionDirection);e.shiftKey?this.moveCursorWithShift(r):this.moveCursorWithoutShift(r),0!==r&&(this.setSelectionInBoundaries(),this.abortCursorAnimation(),this._currentCursorOpacity=1,this.initDelayedCursor(),this._fireSelectionChanged(),this._updateTextarea())},moveCursorWithShift:function(t){var e="left"===this._selectionDirection?this.selectionStart+t:this.selectionEnd+t;return this.setSelectionStartEndWithShift(this.selectionStart,this.selectionEnd,e),0!==t},moveCursorWithoutShift:function(t){return t<0?(this.selectionStart+=t,this.selectionEnd=this.selectionStart):(this.selectionEnd+=t,this.selectionStart=this.selectionEnd),0!==t},moveCursorLeft:function(t){0===this.selectionStart&&0===this.selectionEnd||this._moveCursorLeftOrRight("Left",t)},_move:function(t,e,i){var r;if(t.altKey)r=this["findWordBoundary"+i](this[e]);else{if(!t.metaKey&&35!==t.keyCode&&36!==t.keyCode)return this[e]+="Left"===i?-1:1,!0;r=this["findLineBoundary"+i](this[e])}if(void 0!==typeof r&&this[e]!==r)return this[e]=r,!0},_moveLeft:function(t,e){return this._move(t,e,"Left")},_moveRight:function(t,e){return this._move(t,e,"Right")},moveCursorLeftWithoutShift:function(t){var e=!0;return this._selectionDirection="left",this.selectionEnd===this.selectionStart&&0!==this.selectionStart&&(e=this._moveLeft(t,"selectionStart")),this.selectionEnd=this.selectionStart,e},moveCursorLeftWithShift:function(t){return"right"===this._selectionDirection&&this.selectionStart!==this.selectionEnd?this._moveLeft(t,"selectionEnd"):0!==this.selectionStart?(this._selectionDirection="left",this._moveLeft(t,"selectionStart")):void 0},moveCursorRight:function(t){this.selectionStart>=this.text.length&&this.selectionEnd>=this.text.length||this._moveCursorLeftOrRight("Right",t)},_moveCursorLeftOrRight:function(t,e){var i="moveCursor"+t+"With";this._currentCursorOpacity=1,i+=e.shiftKey?"Shift":"outShift",this[i](e)&&(this.abortCursorAnimation(),this.initDelayedCursor(),this._fireSelectionChanged(),this._updateTextarea())},moveCursorRightWithShift:function(t){return"left"===this._selectionDirection&&this.selectionStart!==this.selectionEnd?this._moveRight(t,"selectionStart"):this.selectionEnd!==this.text.length?(this._selectionDirection="right",this._moveRight(t,"selectionEnd")):void 0},moveCursorRightWithoutShift:function(t){var e=!0;return this._selectionDirection="right",this.selectionStart===this.selectionEnd?(e=this._moveRight(t,"selectionStart"),this.selectionEnd=this.selectionStart):this.selectionStart=this.selectionEnd,e},removeChars:function(t){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(t):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.set("dirty",!0),this.setSelectionEnd(this.selectionStart),this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(t){if(0!==this.selectionStart)if(t.metaKey){var e=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(e,this.selectionStart),this.setSelectionStart(e)}else if(t.altKey){var i=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(i,this.selectionStart),this.setSelectionStart(i)}else this._removeSingleCharAndStyle(this.selectionStart),this.setSelectionStart(this.selectionStart-1)}}),function(){var t=fabric.util.toFixed,e=fabric.Object.NUM_FRACTION_DIGITS;fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(t,e,i,r,n,s){this._getLineStyle(t)?this._setSVGTextLineChars(t,e,i,r,s):fabric.Text.prototype._setSVGTextLineText.call(this,t,e,i,r,n)},_setSVGTextLineChars:function(t,e,i,r,n){for(var s=this._textLines[t],o=0,a=this._getLineLeftOffset(this._getLineWidth(this.ctx,t))-this.width/2,h=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t),l=0,u=s.length;l\n'].join("")},_createTextCharSpan:function(i,r,n,s,o){var a=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text",getSvgFilter:fabric.Object.prototype.getSvgFilter},r));return['\t\t\t',fabric.util.string.escapeXml(i),"\n"].join("")}})}(),function(t){"use strict";var e=t.fabric||(t.fabric={});e.Textbox=e.util.createClass(e.IText,e.Observable,{type:"textbox",minWidth:20,dynamicMinWidth:2,__cachedLines:null,lockScalingY:!0,lockScalingFlip:!0,noScaleCache:!1,initialize:function(t,i){this.callSuper("initialize",t,i),this.setControlsVisibility(e.Textbox.getTextboxControlVisibility()),this.ctx=this.objectCaching?this._cacheContext:e.util.createCanvasElement().getContext("2d"),this._dimensionAffectingProps.push("width")},_initDimensions:function(t){this.__skipDimension||(t||(t=e.util.createCanvasElement().getContext("2d"),this._setTextStyles(t),this.clearContextTop()),this.dynamicMinWidth=0,this._textLines=this._splitTextIntoLines(t),this.dynamicMinWidth>this.width&&this._set("width",this.dynamicMinWidth),this._clearCache(),this.height=this._getTextHeight(t))},_generateStyleMap:function(){for(var t=0,e=0,i=0,r={},n=0;n0?(e=0,i++,t++):" "===this.text[i]&&n>0&&(e++,i++),r[n]={line:t,offset:e},i+=this._textLines[n].length,e+=this._textLines[n].length;return r},_getStyleDeclaration:function(t,e,i){if(this._styleMap){var r=this._styleMap[t];if(!r)return i?{}:null;t=r.line,e=r.offset+e}return this.callSuper("_getStyleDeclaration",t,e,i)},_setStyleDeclaration:function(t,e,i){var r=this._styleMap[t];t=r.line,e=r.offset+e,this.styles[t][e]=i},_deleteStyleDeclaration:function(t,e){var i=this._styleMap[t];t=i.line,e=i.offset+e,delete this.styles[t][e]},_getLineStyle:function(t){var e=this._styleMap[t];return this.styles[e.line]},_setLineStyle:function(t,e){var i=this._styleMap[t];this.styles[i.line]=e},_deleteLineStyle:function(t){var e=this._styleMap[t];delete this.styles[e.line]},_wrapText:function(t,e){var i,r=e.split(this._reNewline),n=[];for(i=0;i=this.width&&!d?(n.push(s),s="",r=l,d=!0):r+=g,d||(s+=c),s+=a,u=this._measureText(t,c,i,h),h++,d=!1,l>f&&(f=l);return p&&n.push(s),f>this.dynamicMinWidth&&(this.dynamicMinWidth=f-g),n},_splitTextIntoLines:function(t){t=t||this.ctx;var e=this.textAlign;this._styleMap=null,t.save(),this._setTextStyles(t),this.textAlign="left";var i=this._wrapText(t,this.text);return this.textAlign=e,t.restore(),this._textLines=i,this._styleMap=this._generateStyleMap(),i},setOnGroup:function(t,e){"scaleX"===t&&(this.set("scaleX",Math.abs(1/e)),this.set("width",this.get("width")*e/("undefined"==typeof this.__oldScaleX?1:this.__oldScaleX)),this.__oldScaleX=e)},get2DCursorLocation:function(t){"undefined"==typeof t&&(t=this.selectionStart);for(var e=this._textLines.length,i=0,r=0;r=h.getMinWidth()?(h.set("width",c),!0):void 0},fabric.Group.prototype._refreshControlsVisibility=function(){if("undefined"!=typeof fabric.Textbox)for(var t=this._objects.length;t--;)if(this._objects[t]instanceof fabric.Textbox)return void this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility())};var e=fabric.util.object.clone;fabric.util.object.extend(fabric.Textbox.prototype,{_removeExtraneousStyles:function(){for(var t in this._styleMap)this._textLines[t]||delete this.styles[this._styleMap[t].line]},insertCharStyleObject:function(t,e,i){var r=this._styleMap[t];t=r.line,e=r.offset+e,fabric.IText.prototype.insertCharStyleObject.apply(this,[t,e,i])},insertNewlineStyleObject:function(t,e,i){var r=this._styleMap[t];t=r.line,e=r.offset+e,fabric.IText.prototype.insertNewlineStyleObject.apply(this,[t,e,i])},shiftLineStyles:function(t,i){var r=e(this.styles),n=this._styleMap[t];t=n.line;for(var s in this.styles){var o=parseInt(s,10);o>t&&(this.styles[o+i]=r[o],r[o-i]||delete this.styles[o])}},_getTextOnPreviousLine:function(t){for(var e=this._textLines[t-1];this._styleMap[t-2]&&this._styleMap[t-2].line===this._styleMap[t-1].line;)e=this._textLines[t-2]+e,t--;return e},removeStyleObject:function(t,e){var i=this.get2DCursorLocation(e),r=this._styleMap[i.lineIndex],n=r.line,s=r.offset+i.charIndex;this._removeStyleObject(t,i,n,s)}})}(),function(){var t=fabric.IText.prototype._getNewSelectionStartFromOffset;fabric.IText.prototype._getNewSelectionStartFromOffset=function(e,i,r,n,s){n=t.call(this,e,i,r,n,s);for(var o=0,a=0,h=0;h=n));h++)"\n"!==this.text[o+a]&&" "!==this.text[o+a]||a++;return n-h+a}}(),function(){function request(t,e,i){var r=URL.parse(t);r.port||(r.port=0===r.protocol.indexOf("https:")?443:80);var n=0===r.protocol.indexOf("https:")?HTTPS:HTTP,s=n.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(t){var r="";e&&t.setEncoding(e),t.on("end",function(){i(r)}),t.on("data",function(e){200===t.statusCode&&(r+=e)})});s.on("error",function(t){t.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(t.message),i(null)}),s.end()}function requestFs(t,e){var i=require("fs");i.readFile(t,function(t,i){if(t)throw fabric.log(t),t;e(i)})}if("undefined"==typeof document||"undefined"==typeof window){var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(t,e,i){function r(r){r?(n.src=new Buffer(r,"binary"),n._src=t,e&&e.call(i,n)):(n=null,e&&e.call(i,null,!0))}var n=new Image;t&&(t instanceof Buffer||0===t.indexOf("data"))?(n.src=n._src=t,e&&e.call(i,n)):t&&0!==t.indexOf("http")?requestFs(t,r):t?request(t,"binary",r):e&&e.call(i,t)},fabric.loadSVGFromURL=function(t,e,i){t=t.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),0!==t.indexOf("http")?requestFs(t,function(t){fabric.loadSVGFromString(t.toString(),e,i)}):request(t,"",function(t){fabric.loadSVGFromString(t,e,i)})},fabric.loadSVGFromString=function(t,e,i){var r=(new DOMParser).parseFromString(t);fabric.parseSVGDocument(r.documentElement,function(t,i){e&&e(t,i)},i)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.createCanvasForNode=function(t,e,i,r){r=r||i;var n=fabric.document.createElement("canvas"),s=new Canvas(t||600,e||600,r),o=new Canvas(t||600,e||600,r);n.style={},n.width=s.width,n.height=s.height,i=i||{},i.nodeCanvas=s,i.nodeCacheCanvas=o;var a=fabric.Canvas||fabric.StaticCanvas,h=new a(n,i);return h.nodeCanvas=s,h.nodeCacheCanvas=o,h.contextContainer=s.getContext("2d"),h.contextCache=o.getContext("2d"),h.Font=Canvas.Font,h};var originaInitStatic=fabric.StaticCanvas.prototype._initStatic;fabric.StaticCanvas.prototype._initStatic=function(t,e){t=t||fabric.document.createElement("canvas"),this.nodeCanvas=new Canvas(t.width,t.height),this.nodeCacheCanvas=new Canvas(t.width,t.height),originaInitStatic.call(this,t,e),this.contextContainer=this.nodeCanvas.getContext("2d"),this.contextCache=this.nodeCacheCanvas.getContext("2d"),this.Font=Canvas.Font; -},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(t){return this.nodeCanvas.createJPEGStream(t)},fabric.StaticCanvas.prototype._initRetinaScaling=function(){if(this._isRetinaScaling())return this.lowerCanvasEl.setAttribute("width",this.width*fabric.devicePixelRatio),this.lowerCanvasEl.setAttribute("height",this.height*fabric.devicePixelRatio),this.nodeCanvas.width=this.width*fabric.devicePixelRatio,this.nodeCanvas.height=this.height*fabric.devicePixelRatio,this.contextContainer.scale(fabric.devicePixelRatio,fabric.devicePixelRatio),this},fabric.Canvas&&(fabric.Canvas.prototype._initRetinaScaling=fabric.StaticCanvas.prototype._initRetinaScaling);var origSetBackstoreDimension=fabric.StaticCanvas.prototype._setBackstoreDimension;fabric.StaticCanvas.prototype._setBackstoreDimension=function(t,e){return origSetBackstoreDimension.call(this,t,e),this.nodeCanvas[t]=e,this},fabric.Canvas&&(fabric.Canvas.prototype._setBackstoreDimension=fabric.StaticCanvas.prototype._setBackstoreDimension)}}(); \ No newline at end of file diff --git a/reo-online-editor/group.html b/reo-online-editor/group.html deleted file mode 100644 index 7e92ea02..00000000 --- a/reo-online-editor/group.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - Online Reo editor - - - -
-
- - -
-
- - - - - -
SelectNew component
- -
-
-

Channels

-
- - - - -
- Sync
- Sync -
-
-
- Please use a browser that supports canvas. -
-
- - - - \ No newline at end of file diff --git a/reo-online-editor/group.js b/reo-online-editor/group.js deleted file mode 100644 index 921e4b86..00000000 --- a/reo-online-editor/group.js +++ /dev/null @@ -1,116 +0,0 @@ -(function() { - var canvas = this.__canvas = new fabric.Canvas('c', { selection: false }); - fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'; - var active, isDown, origX, origY, origLeft, origTop; - var mode = 'select'; - var id = '0'; - - var rect = new fabric.Rect({ - left: 100, - top: 100, - width: 300, - height: 300, - fill: 'transparent', - stroke: '#000', - strokeWidth: 1, - hoverCursor: 'default', - originX: 'left', - originY: 'top', - //hasBorders: false, - //hasControls: false, - class: 'component', - id: 'rect' - }); - - canvas.add(rect); - - var c = new fabric.Circle({ - left: 200, - top: 200, - strokeWidth: 5, - radius: 12, - fill: '#fff', - stroke: '#000', - hasControls: false, - class: 'node', - component: rect, - id: 'c' - }); - - canvas.add(c); - - canvas.on('mouse:down', function(e) { - isDown = true; - var pointer = canvas.getPointer(e.e); - origX = pointer.x; - origY = pointer.y; - var p = canvas.getActiveObject(); - if (p) - console.log(p); - if (p && p.class == 'component') { - var copy = p.clone(); - copy.set({ - 'class': p.class, - 'id': p.id - }); - var group = new fabric.Group([ copy ], { - left: p.left, - top: p.top, - originX: 'left', - originY: 'top', - class: 'group' - }); - canvas.forEachObject(function(obj) { - if (obj.class != 'component' && obj.component == p) { - var copy = obj.clone(); - copy.set({ - 'class': obj.class, - 'component': obj.component, - 'id': obj.id - }); - group.addWithUpdate(copy); - canvas.remove(obj); - } - }); - canvas.remove(p); - canvas.renderAll(); - canvas.add(group); - canvas.setActiveObject(group); - origLeft = group.left; - origTop = group.top; - } - }); //mouse:down - - canvas.on('mouse:move', function(e){ - if (!isDown) - return; - var p = canvas.getActiveObject(); - if (!p) - return; - var pointer = canvas.getPointer(e.e); - if (p.class == 'group') { - p.set({left: origLeft + pointer.x - origX}); - p.set({top: origTop + pointer.y - origY}); - p.setCoords(); - canvas.renderAll(); - } - }); //mouse:move - - canvas.on('mouse:up', function(e){ - isDown = false; - var p = canvas.getActiveObject(); - if (p && p.class == 'group') { - var items = p._objects; - p._restoreObjectsState(); - canvas.remove(p); - var comp = items[0]; - canvas.add(comp); - for (var i = 1; i < items.length; i++) { - items[i].set({'component': comp}); - canvas.add(items[i]); - } - canvas.renderAll(); - } - }); //mouse:up - -})(); \ No newline at end of file diff --git a/reo-online-editor/playground/CAVEATS b/reo-online-editor/playground/CAVEATS deleted file mode 100644 index ed3280fe..00000000 --- a/reo-online-editor/playground/CAVEATS +++ /dev/null @@ -1,19 +0,0 @@ -- Parts of the program assume that read and write nodes have only one end, for - simplicity. - -- The UI for choice of non-deterministic alternates isn't very intuitive, and - currently cannot choose alternates in ticks where there are no nodes. - -- The animation of components is sometimes wrong, timing-wise. The animation - assumes that data arrives at all of the output ports at the moment there is - data on one of the input ports. See the lossyChooser (#debug-lossyChooser and - #debug-component) for an example where this assumption is not met. - - The right way to do it would probably be to consider pending data items - inside components on a step-by-step basis, rather than just looking at the - final coloring. For the example connector in the Playground (Or-selector), - this doesn't matter. - -- Filters and other data-dependent channels aren't supported. All data items - are considered identical (since the program only keeps track of on which ends - there are pending data items). diff --git a/reo-online-editor/playground/animation.js b/reo-online-editor/playground/animation.js deleted file mode 100644 index 21bbcd5e..00000000 --- a/reo-online-editor/playground/animation.js +++ /dev/null @@ -1,567 +0,0 @@ -"use strict"; - -// Polyfills for older browsers -window.requestAnimationFrame = window.requestAnimationFrame || (function(){ - return window.webkitRequestAnimationFrame - || window.mozRequestAnimationFrame - || function(callback){ - return window.setTimeout(callback, 1000 / 60, Date.now()); - }; -})(); - -window.cancelAnimationFrame = window.cancelAnimationFrame || (function(){ - return window.webkitCancelAnimationFrame - || window.mozCancelAnimationFrame - || function(requestID){ - return window.clearTimeout(requestID); - }; -})(); - -var Reo = Reo || {}; - -Reo.Animation = function(canvas, options){ - // Animation object. - if(!(this instanceof Reo.Animation)) - return; - - this.options = options || { - labelMerge: true, - stepTime: 3000 - }; - - this.canvas = new Canvas(canvas); - this.connector = null; - - this.io = []; - - this.pending = []; - this.blocked = []; - - this.state = "idle"; - this.tickStart = -1; - - var lastFrame = -1; - (this.startAnimation = (function(n){ - this.animationFrame(n); - lastFrame = window.requestAnimationFrame(this.startAnimation); - }).bind(this))(); - - this.stopAnimation = function(){ - window.cancelAnimationFrame(lastFrame); - }; - - this.finishCallback = null; -}; - -Reo.Animation.prototype = { - constructor: Reo.Animation.prototype.constructor, - set connector(v){ - this._connector = v; - this.state = "idle"; - this.tickStart = -1; - }, - get connector(){ - return this._connector; - } -}; - -Reo.Animation.prototype.drawChannel = function(chan){ - // Draw a channel [chan] with optional coloring [col]. - // get channel end coordinates - var type = chan.animationType || chan.type; - var ends = chan.ends, - from = ends[0].node.coord, - to = ends[1].node.coord; - - // determine angle towards to-end - var ang = Math.atan2(to[0] - from[0], to[1] - from[1]); - - // adjust coords to end at the node rather than inside the node - var p04 = [from[0] + Math.sin(ang) * 5, - from[1] + Math.cos(ang) * 5], - p44 = [to[0] - Math.sin(ang) * 5, - to[1] - Math.cos(ang) * 5]; - - // calculate points partway along the channel (fourths) - var p24 = [(from[0] + to[0]) / 2, - (from[1] + to[1]) / 2], - p14 = [(p24[0] + from[0]) / 2, - (p24[1] + from[1]) / 2], - p34 = [(p24[0] + to[0]) / 2, - (p24[1] + to[1]) / 2]; - - // flow marks - if(ends[0].color=="-"){ - this.canvas.drawLine(from, p24, - {stroke: "rgba(0, 0, 255, 0.5)", lineWidth: 15}); - } - if(ends[1].color=="-"){ - this.canvas.drawLine(p24, to, - {stroke: "rgba(0, 0, 255, 0.5)", lineWidth: 15}); - } - - // the channel itself - type.draw && type.draw(this.canvas, p04, p44, [p14, p24, p34], ang); - - // no-flow marks - if(ends[0].color==">" || ends[0].color=="<"){ - var n = (ends[0].color==">")?1:-1; - // flip if this is not a source end - if(chan.sinks.indexOf(ends[0]) >= 0) - n = -n; - - this.canvas.drawPolygon(p14, - {n: 3, radius: 10, - angle: n * (Math.PI / 2) - ang}, - {stroke: "#c00", lineWidth: 3}); - } - if(ends[1].color==">" || ends[1].color=="<"){ - var n = (ends[1].color==">")?1:-1; - // flip if this is not a sink end - if(chan.sources.indexOf(ends[1]) >= 0) - n = -n; - - this.canvas.drawPolygon(p34, - {n: 3, radius: 10, - angle: n * (Math.PI / 2) - ang}, - {stroke: "#c00", lineWidth: 3}); - } -}; - -Reo.Animation.prototype.drawConnector = function(){ - // Draws the current connector to the canvas. - // draw outgoing channels - for(var i in this.connector.channels){ - var chan = this.connector.channels[i]; - this.drawChannel(chan); - } - - // draw components - for(var i in this.connector.components){ - var comp = this.connector.components[i]; - this.canvas.drawRect(comp.coord, comp.size[0], comp.size[1] + 20, - {center: true}, {stroke: "#000", fill: "#fff"}); - - var label = comp.connector.id + (Reo.debug?(" (" + comp.id + ")"):""); - this.canvas.drawText(comp.coord, label, - {align: "center", baseline: "middle"}); - } - - // draw nodes - for(var i in this.connector.nodes){ - var node = this.connector.nodes[i]; - switch(node.type){ - case "read": - case "write": - this.canvas.drawRect(node.coord, 10, 10, {center: true}, - {stroke: "#000", fill: "#fff"}); - break; - case "xrouter": - this.canvas.drawCircle(node.coord, 8, - {stroke: "#000", fill: "#fff"}); - - this.canvas.drawPolygon(node.coord, - {n: 2, radius: 8, angle: 1 * (Math.PI / 4)}); - this.canvas.drawPolygon(node.coord, - {n: 2, radius: 8, angle: 3 * (Math.PI / 4)}); - break; - default: - this.canvas.drawCircle(node.coord, 5, - {stroke: "#000", fill: "#fff"}); - break; - } - - // don't place labels on merge nodes - if(!this.options.labelMerge && node.type=="merge") - continue; - - // naïve heuristic for label placement - var xOff = node.coord[0] - (this.canvas.canvas.width / 2), - yOff = node.coord[1] - (this.canvas.canvas.height / 2); - if(xOff!=0) - xOff = ((xOff / Math.abs(xOff)) * 15); - if(yOff!=0) - yOff = ((yOff / Math.abs(yOff)) * 15); - if(xOff==0 && yOff==0) - yOff = -15; - - // draw label - this.canvas.drawText([node.coord[0] + xOff, node.coord[1] + yOff], - node.id, {align: "center", baseline: "middle"}); - } -}; - -Reo.Animation.prototype._originStep = function(t){ - // Draw origin step. - // Returns whether animation is finished. - var rel = 2 * (t % this.options.stepTime) / this.options.stepTime, - finished = (rel > 1); - - var noItems = true; - for(var i in this.connector.channels){ - var chan = this.connector.channels[i], - ends = chan.ends; - if(!chan.type.origin) - continue; - - var from = ends[0].node.coord, - to = ends[1].node.coord, - mid = [(from[0] + to[0]) / 2, - (from[1] + to[1]) / 2]; - - for(var j in chan.sinks){ - var sink = chan.sinks[j], - sinkCoord = sink.node.coord; - if(sink.color!="-") - continue; - - noItems = false; - - chan.animationType = chan.type.out; - this.canvas.drawPolygon([mid[0] + (rel * (sinkCoord[0] - mid[0])), - mid[1] + (rel * (sinkCoord[1] - mid[1]))], - {n: 5, radius: 8, angle: -Math.PI / 2}, - {fill: "#fc0"}); - } - } - - return finished || noItems; -}; - -Reo.Animation.prototype._propagateInStep = function(t){ - // Draw propagation step. - // Returns whether animation is finished. - var rel = 2 * (t % this.options.stepTime) / this.options.stepTime, - finished = false; - if(rel >= 1){ - this.pending.forEach(function(end){ - var chan = end.channel; - if(end.color!="-" || chan.sinks.indexOf(end) >= 0) - return; - - if(chan.animationType) - chan.animationType = chan.animationType.in; - else - chan.animationType = chan.type.in; - }); - finished = true; - } - - var noItems = true; - for(var i in this.pending){ - var end = this.pending[i], - chan = end.channel, - ends = chan.ends; - - // calculate middle - var from = ends[0].node.coord, - to = ends[1].node.coord, - mid = [(from[0] + to[0]) / 2, - (from[1] + to[1]) / 2]; - - // not incoming or no flow - if(end.color!="-" || chan.sinks.indexOf(end) >= 0) - continue; - - noItems = false; - - var here = end.node.coord; - this.canvas.drawPolygon([here[0] + (rel * (mid[0] - here[0])), - here[1] + (rel * (mid[1] - here[1]))], - {n: 5, radius: 8, angle: -Math.PI / 2}, - {fill: "#fc0"}); - } - - return finished || noItems; -}; - -Reo.Animation.prototype._propagateOutStep = function(t){ - // Draw propagation step. - // Returns whether animation is finished. - var rel = 2 * (t % this.options.stepTime) / this.options.stepTime, - finished = (rel > 1); - - var noItems = true; - for(var i in this.pending){ - var end = this.pending[i], - chan = end.channel, - ends = chan.ends; - if(chan.type.origin) - continue; - - // calculate middle - var from = ends[0].node.coord, - to = ends[1].node.coord, - mid = [(from[0] + to[0]) / 2, - (from[1] + to[1]) / 2]; - - // find outgoing end (we have incoming end) - end = chan.ends[1 - chan.ends.indexOf(end)]; - - // not outgoing or no flow - if(end.color!="-" || chan.sources.indexOf(end) >= 0) - continue; - - noItems = false; - - var here = end.node.coord; - this.canvas.drawPolygon([mid[0] + (rel * (here[0] - mid[0])), - mid[1] + (rel * (here[1] - mid[1]))], - {n: 5, radius: 8, angle: -Math.PI / 2}, - {fill: "#fc0"}); - } - - return finished || noItems; -}; - -Reo.Animation.prototype.animationFrame = function(n){ - // Draw a single frame of animation. - this.canvas.clear(); - if(!this.connector) - return; - - function _checkBlockers(end, pending, blocked){ - // Returns if an [end]'s channel is blocking and its other end is not - // present, and move it to [blocked] if so. If the other end is also - // blocking, move the other end to [pending]. - if(end.channel.sinks.length==0){ - // drain channel - var ends = end.channel.sources, - there = ends[1 - ends.indexOf(end)]; - - var bi = blocked.indexOf(there); - if(bi >= 0){ - pending.push(blocked.splice(bi)[0]); - }else if(pending.indexOf(there) < 0 && there.color=="-"){ - blocked.push(end); - return true; - } - } - return false; - } - - this.drawConnector(); - - if(Reo.debug){ - // show activity with rotating throbber - this.canvas.drawPolygon([30, 30], - {n: 5, radius: 15, - angle: (n / 400) % (2 * Math.PI)}, - {stroke: "#080", fill: "#0c0"}); - - // display which state we're in - this.canvas.drawText([60, 30], this.state, {baseline: "middle"}); - - // display amount of colorings - if(this.connector._colorings){ - var colTotal = this.connector._colorings.length + " total colorings", - colPeak = this.connector._peak + " at peak"; - this.canvas.drawText([this.canvas.canvas.width - 30, 30], - colTotal + ", " + colPeak, - {align: "right", baseline: "middle"}); - this.canvas.drawText([this.canvas.canvas.width - 30, 50], - "(w/o nextables)", - {align: "right", baseline: "middle"}); - } - } - - var t = n - this.tickStart; - switch(this.state){ - case "start": - this.pending = []; - this.blocked = []; - - for(var i in this.io){ - var end = this.io[i]; - - if(end.color!="-" || end.node.type!="write") - continue; - - if(!_checkBlockers(end, this.pending, this.blocked)) - this.pending.push(end); - } - - // unmark all ends - for(var i in this.connector.nodes){ - var node = this.connector.nodes[i]; - node.ends.forEach(function(n){ - n.visited = false; - }); - } - - // set origin sinks to pending - var hasOrigins = false; - for(var i in this.connector.channels){ - var chan = this.connector.channels[i]; - - // clear intermediate type - chan.animationType = null; - - if(!chan.type.origin) - continue; - - for(var j in chan.sinks){ - var end = chan.sinks[j]; - // even though there may be no sinks (and thus no 'regular' pending), - // we should still animate the origin step (sink may be read node) - if(end.color=="-") - hasOrigins = true; - else - continue; - - end.node.sinks.forEach(function(n){ - if(n.color!="-") - return; - - if(!_checkBlockers(n, this.pending, this.blocked)) - this.pending.push(n); - }, this); - } - } - - t = 0; - this.tickStart = n; - - if(this.pending.length==0 && !hasOrigins) - this.state = "delay"; - else - this.state = "origin"; - - Reo._debug.clear(); - Reo._debug.log("n: [" + this.pending.map(function(n){ - return n.node.id + "/" + n.channel.id; - }) + "]\nb: [" + this.blocked.map(function(n){ - return n.node.id + "/" + n.channel.id; - }) + "]"); - break; - - // there are no data items; delay a bit - case "delay": - if(t >= (0.5 * this.options.stepTime)) - this.state = "done"; - break; - - // draw data items originating from channels - case "origin": - if(this._originStep(t)){ - this.state = "propagateIn"; - this.tickStart = n; - } - break; - - // draw data items being propagated from pending nodes into channels - case "propagateIn": - if(this._propagateInStep(t)){ - this.state = "propagateOut"; - this.tickStart = n; - } - break; - - // draw data items being propagated from channels into next pending nodes - case "propagateOut": - if(this._propagateOutStep(t)){ - this.state = "propagateNext"; - this.tickStart = n; - } - break; - - // determine next pending nodes - case "propagateNext": - var next = [], - block = []; - for(var i; i = this.pending.shift();){ - i.visited = true; - - // there is only one sink (spouts are origins) - var sink = i.channel.sinks[0]; - if(!sink) - continue; - sink = sink.node; - - // determine which sinks are outgoing - var sinks = sink.sinks; - if(sink.type=="edge"){ - // TODO/1: this will output when the first of the inputs arrive, - // TODO/2: which is not always correct (see lossyChooser). - var comp = this.connector.map[sink.id].component, - ends = []; - // get all of the component's read nodes - for(var j in comp.connector.nodes){ - var node = comp.connector.nodes[j]; - if(node.type=="read") - ends.push(node.id); - } - - sinks = ends.reduce((function(ret, n){ - return ret.concat(this.connector.nodes[comp.map[n]].sinks); - }).bind(this), []); - } - - // add all outgoing sink ends - for(var j in sinks){ - var end = sinks[j]; - - if(_checkBlockers(end, next, block) - || end.color!="-" || end.visited) - continue; - - next.push(end); - end.visited = true; - } - } - - // check for unblocks - for(var i in this.blocked){ - var ends = this.blocked[i].channel.sources, - here = this.blocked[i], - there = ends[1 - ends.indexOf(here)]; - - var j = block.indexOf(there); - if(j >= 0){ - next.push(here); - next.push(block.splice(j)[0]); - }else{ - block.push(here); - } - - continue; - } - - this.pending = next; - this.blocked = block; - - Reo._debug.log("n: [" + this.pending.map(function(n){ - return n.node.id + "/" + n.channel.id; - }) + "]\nb: [" + this.blocked.map(function(n){ - return n.node.id + "/" + n.channel.id; - }) + "]"); - - if(this.pending.length > 0){ - this.tickStart = n; - this.state = "propagateIn"; - this._propagateInStep(0); - }else{ - this.state = "done"; - } - break; - - case "done": - this.state = "idle"; - (this.finishCallback) && this.finishCallback(); - break; - - default: - break; - } - - // draw all blocked items - // (except origin blockers since those haven't 'propagated out' yet) - if(this.state=="idle" || this.state=="origin") - return; - - for(var i in this.blocked){ - this.canvas.drawPolygon(this.blocked[i].node.coord, - {n: 5, radius: 8, angle: -Math.PI / 2}, - {fill: "#c00"}); - } -}; diff --git a/reo-online-editor/playground/canvas.js b/reo-online-editor/playground/canvas.js deleted file mode 100644 index c305b0cf..00000000 --- a/reo-online-editor/playground/canvas.js +++ /dev/null @@ -1,223 +0,0 @@ -"use strict"; - -function Canvas(canvas, options){ - // Canvas abstraction object. - if(!(this instanceof Canvas)) - return; - - if(!(canvas instanceof HTMLCanvasElement)) - throw new TypeError("Canvas must be HTMLCanvasElement"); - - this.options = options || { - style: { - lineWidth: 2, - fill: "#fff", - stroke: "#000", - font: "bold 1em sans-serif" - } - }; - - this.canvas = canvas; - this.context = this.canvas.getContext("2d"); - if(!this.context) - throw new Error("Cannot get CanvasRenderingContext2D"); -} - -Canvas.prototype._endPath = function(style){ - // End a path with stroke and/or fill. If there is no style, the path will - // be stroked. - this.context.lineWidth = (style && style.lineWidth) - || this.options.style.lineWidth; - - this.context.fillStyle = (style && style.fill) - || this.options.style.fill; - (style && style.fill) && this.context.fill(); - - this.context.strokeStyle = (style && style.stroke) - || this.options.style.stroke; - (style && !style.stroke) || this.context.stroke(); -}; - -Canvas.prototype.clear = function(){ - // Clear the canvas. - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); -}; - -Canvas.prototype.drawLine = function(from, to, style){ - // Draw a line from [from] to [to]. - this.context.beginPath(); - this.context.moveTo(from[0], from[1]); - this.context.lineTo(to[0], to[1]); - this._endPath(style); -}; - -Canvas.prototype.drawPath = function(coords, style){ - // Draw a path along given [coords]. - this.context.beginPath(); - - var waypoint = coords.shift(); - this.context.moveTo(waypoint[0], waypoint[1]); - while(waypoint = coords.shift()){ - this.context.lineTo(waypoint[0], waypoint[1]); - } - - this._endPath(style); -}; - -Canvas.prototype.drawDashedLine = function(from, to, style){ - // Draw a dashed line from [from] to [to]. - this.context.save(); - this.context.translate(from[0], from[1]); - this.context.beginPath(); - - var dx = to[0] - from[0], - dy = to[1] - from[1]; - - // rotate so [from] is up - var angle = (2 * Math.PI) - Math.atan2(dx, dy); - this.context.rotate(angle); - this.context.moveTo(0, 0); - - var len = Math.sqrt(dx * dx + dy * dy); - - var mid = 0; - while(mid < len){ - mid += 10; - this.context.lineTo(0, mid > len?len:mid); - mid += 10; - this.context.moveTo(0, mid); - } - - this._endPath(style); - this.context.restore(); -}; - -Canvas.prototype.drawCircle = function(coord, radius, style){ - // Draw a circle of [radius] with its center at [coord]. - this.context.beginPath(); - this.context.arc(coord[0], coord[1], radius, 0, 2 * Math.PI); - this._endPath(style); -}; - -Canvas.prototype.drawRect = function(coord, width, height, opt, style){ - // Draw a rectangle of [width]x[height] at [coord]. - // Options: - // angle: angle around [coord] - // center: [coord] is center of rect rather than top left - this.context.save(); - this.context.translate(coord[0], coord[1]); - (opt && opt.angle) && this.context.rotate(opt.angle); - this.context.beginPath(); - - // draw center at coord if [opt.center] is true - var mul = (opt && opt.center)?-0.5:0; - this.context.rect(mul * width, mul * height, width, height); - - this._endPath(style); - this.context.restore(); -}; - -Canvas.prototype.drawPolygon = function(coord, opt, style){ - // Draw a [opt.n]-sided polygon of [opt.radius], centered at [coord]. - // The first point will be to the right of the center point. - // Options: - // n: amount of points (required) - // radius: radius around center (required) - // angle: angle around [coord] - this.context.save(); - this.context.translate(coord[0], coord[1]); - (opt && opt.angle) && this.context.rotate(opt.angle); - - this.context.beginPath(); - for(var i = 1; i <= opt.n; i++){ - this.context.lineTo(Math.cos(i * (2 * Math.PI / opt.n)) * opt.radius, - Math.sin(i * (2 * Math.PI / opt.n)) * opt.radius); - } - this.context.closePath(); - - this._endPath(style); - this.context.restore(); -}; - -Canvas.prototype.drawText = function(coord, text, opt, style){ - // Draw [text] at [coord]. The text will always be filled, and optionally - // stroked if one is specified. - // Options: - // angle: angle around [coord] - // align: horizontal text alignment - // baseline: vertical text alignment - // font: text font - // max: maximum width for text (shrink text if exceeded) - this.context.save(); - this.context.translate(coord[0], coord[1]); - (opt && opt.angle) && this.context.rotate(opt.angle); - - this.context.textAlign = (opt && opt.align) || "start"; - this.context.textBaseline = (opt && opt.baseline) || "alphabetic"; - this.context.font = (opt && opt.font) || this.options.style.font; - - this.context.fillStyle = (style && style.fill) - || this.options.style.stroke; - if(opt && opt.max) - this.context.fillText(text, 0, 0, opt.max); - else - this.context.fillText(text, 0, 0); - - if(style && style.stroke){ - this.context.strokeStyle = style.stroke; - if(opt && opt.max) - this.context.strokeText(text, 0, 0, opt.max); - else - this.context.strokeText(text, 0, 0); - } - - this.context.restore(); -}; - -Canvas.prototype.drawArrow = function(from, to, opt, style){ - // Draw an arrow from [from] to [to]. - // Options: - // dashed: whether to draw dashed arrow - // text: text to add to the edge - if(opt && opt.dashed) - this.drawDashedLine(from, to, style); - else - this.drawLine(from, to, style); - - // draw arrowhead - this.context.save(); - this.context.translate(to[0], to[1]); - this.context.beginPath(); - - var dx = to[0] - from[0], - dy = to[1] - from[1]; - - // rotate so [from] is up - var angle = (2 * Math.PI) - Math.atan2(dx, dy); - this.context.rotate(angle); - - this.context.moveTo(0, 0); - this.context.lineTo(3, -15); - - this.context.lineTo(0, -10); - this.context.lineTo(-3, -15); - this.context.closePath(); - - // always fill arrowhead - this.context.fillStyle = (style && style.stroke) - || this.options.style.stroke; - this.context.fill(); - this._endPath(style); - - // draw text, flip if rotated >= 180deg - if(opt && opt.text){ - var len = Math.sqrt(dx * dx + dy * dy), - side = (angle / Math.PI) < 2; - - this.drawText([side?15:-15, -len / 2], opt.text, - {align: "center", max: len, - angle: (side?1:-1) * (Math.PI / 2)}); - } - - this.context.restore(); -}; diff --git a/reo-online-editor/playground/connector.js b/reo-online-editor/playground/connector.js deleted file mode 100644 index 1bf2263e..00000000 --- a/reo-online-editor/playground/connector.js +++ /dev/null @@ -1,818 +0,0 @@ -"use strict"; - -var Reo = Reo || {}; - -Reo.End = function(node, channel){ - // Reo channel end object. - this.node = node; - this.channel = channel; - - this.color = null; -}; - -Reo.Node = function(parent, id, coord, type){ - // Reo node object. - this.id = id; - this.parent = parent; - - this.coord = coord; - this.type = type; - - this.sources = []; - this.sinks = []; -}; - -Reo.Node.prototype = { - constructor: Reo.Node.prototype.constructor, - get ends(){ - return this.sources.concat(this.sinks); - }, - get colorings(){ - if(this._colorings) - return this._colorings; - - console.info("Generating colorings for " + this.type - + " node " + this.id + "."); - switch(this.type){ - case "read": - case "write": - case "edge": - // coloring of these nodes depends on I/O availability - this._colorings = []; - break; - case "xrouter": - this._colorings = this._getColoringsXrouter(); - break; - default: - this._colorings = this._getColoringsMerge(); - } - - return this._colorings; - } -}; - -Reo.Node.prototype._getColoringsMerge = function(){ - // Returns coloring table for a merge node. - var table = []; - - // flow from a source to all sink ends - // use 'dummy source' to run loop even without sources (for component sinks) - var sources = this.sources.length==0?["dummy-source"]:this.sources; - for(var i in sources){ - var col = {sources: [], sinks: []}; - // flow from a single source end - this.sources.forEach(function(n, j){ - col.sources.push(j==i?"-":">"); - }); - - // to all sink ends - this.sinks.forEach(function(){ - col.sinks.push("-"); - }); - - table.push(col); - } - - // flow is blocked by one of the sink ends - // use 'dummy sink' to run loop even without sinks (for component sources) - var sinks = this.sinks.length==0?["dummy-sink"]:this.sinks; - for(var i in sinks){ - var col = {sources: [], sinks: []}; - // all source ends are blocked - this.sources.forEach(function(){ - col.sources.push(">"); - }); - - // one sink end is blocking - this.sinks.forEach(function(n, j){ - col.sinks.push(i==j?">":"<"); - }); - - table.push(col); - } - - // flow is blocked by lack of sources and all sink ends - // XXX: ideally we'd generate all combinations of blocking/flowing sinks - if(this.sinks.length > 1){ - var col = {sources: [], sinks: []}; - // all source ends are blocked - this.sources.forEach(function(){ - col.sources.push("<"); - }); - - // all sink ends are blocking - this.sinks.forEach(function(){ - col.sinks.push(">"); - }); - - table.push(col); - } - - // flow is blocked by lack of sources - { - var col = {sources: [], sinks: []}; - // all source ends are blocking - this.sources.forEach(function(){ - col.sources.push("<"); - }); - - // all sink ends are blocked - this.sinks.forEach(function(){ - col.sinks.push("<"); - }); - - table.push(col); - } - - // annotate with node identifier - return table.map(function(n){ - var m = {}; - m[this.id] = n; - return m; - }, this); -}; - -Reo.Node.prototype._getColoringsXrouter = function(){ - // Returns coloring table for a exclusive router node. - var table = []; - - // flow from a source to one sink ends - for(var i in this.sources){ - for(var j in this.sinks){ - var col = {sources: [], sinks: []}; - // flow from a single source end - this.sources.forEach(function(n, k){ - col.sources.push(k==i?"-":">"); - }); - - // to a single sink ends - this.sinks.forEach(function(n, k){ - col.sinks.push(k==j?"-":"<"); - }); - - table.push(col); - } - } - - // flow is blocked by all sink ends - { - var col = {sources: [], sinks: []}; - // all source ends are blocked - this.sources.forEach(function(){ - col.sources.push(">"); - }); - - // all sink end is blocking - this.sinks.forEach(function(){ - col.sinks.push(">"); - }); - - table.push(col); - } - - // flow is blocked by lack of sources - { - var col = {sources: [], sinks: []}; - // all source ends are blocked - this.sources.forEach(function(){ - col.sources.push("<"); - }); - - // all sink ends are blocked - this.sinks.forEach(function(){ - col.sinks.push("<"); - }); - - table.push(col); - } - - // annotate with node identifier - return table.map(function(n){ - var m = {}; - m[this.id] = n; - return m; - }, this); -}; - -Reo.Channel = function(parent, id, sources, sinks, type){ - // Reo channel object. - this.id = id; - this.parent = parent; - - this.sources = []; - this.sinks = []; - - this.type = type; - this.typeInit = type; - - if(sources.length + sinks.length > 2) - throw new Error("Channel can not have more than 2 ends"); - - if(("sources" in type.colorings[0])!=(sources.length > 0) - || ("sinks" in type.colorings[0])!=(sinks.length > 0)) - throw new Error("Channel end mismatch"); - - - // add connected source/sinks to every node - for(var i in sources){ - var node = sources[i], - end = new Reo.End(node, this); - this.sources.push(end); - node.sinks.push(end); - } - - for(var i in sinks){ - var node = sinks[i], - end = new Reo.End(node, this); - this.sinks.push(end); - node.sources.push(end); - } -}; - -Reo.Channel.prototype = { - constructor: Reo.Channel.prototype.constructor, - get ends(){ - return this.sources.concat(this.sinks); - }, - get colorings(){ - return this._colorings || (this._colorings = this._getColorings()); - }, - get type(){ - return this._type; - }, - set type(v){ - // delete cached colorings and invalidate connector coloring - this._colorings = null; - this.parent.coloring = null; - return (this._type = v); - }, - get next(){ - var connectorCol = this.parent.coloring; - if(!connectorCol || !this.type.hasNexts) - return undefined; - - // find out which channel coloring corresponds with connector coloring - for(var idx in this.colorings){ - var col = this.colorings[idx], - ok = false; - - // check if all ends match - for(var j in col){ - ok = col[j].sources.every(function(n, i){ - return n==connectorCol[j].sources[i]; - }) && col[j].sinks.every(function(n, i){ - return n==connectorCol[j].sinks[i]; - }); - - if(!ok) - break; - } - - // found a matching coloring - if(ok) - return this.type.colorings[idx].next; - } - - throw new Error("No possible next"); - } -}; - -Reo.Channel.prototype._getColorings = function(){ - // Returns coloring table for this channel. - var table = []; - console.info("Generating colorings for channel " + this.id + "."); - - // generate all possible colorings for this channel - for(var c in this.type.colorings){ - var coloring = this.type.colorings[c]; - var col = {}; - - // add all sources - for(var i in this.sources){ - var source = this.sources[i], - idx = source.node.sinks.indexOf(source); - - var node = {sources: [], sinks: []}; - node.sinks.length = source.node.sinks.length; - node.sinks[idx] = coloring.sources[i]; - col[source.node.id] = node; - } - - // add all sinks - for(var i in this.sinks){ - var sink = this.sinks[i], - idx = sink.node.sources.indexOf(sink); - - var node = {sources: [], sinks: []}; - node.sources.length = sink.node.sources.length; - node.sources[idx] = coloring.sinks[i]; - col[sink.node.id] = node; - } - - table.push(col); - } - - return table; -}; - -Reo.Component = function(parent, id, coord, size, map, connector){ - // Reo (opaque) component object. - this.id = id; - this.parent = parent; - - this.coord = coord; - this.size = size; - - this.map = map; - this.connector = connector; -}; - -Reo.Component.prototype = { - constructor: Reo.Component.prototype.constructor, - get colorings(){ - // get colorings and map ports to the outside world - var cols = this.connector.colorings, - mappedCols = []; - for(var i in cols){ - var col = cols[i], - mappedCol = {}; - for(var j in col){ - // rename internal colorings to not interfere - mappedCol[this.id + "/" + j] = col[j]; - - // for the outside world, our sources are sinks and vice versa - if(j in this.map) - mappedCol[this.map[j]] = {sources: col[j].sinks, - sinks: col[j].sources}; - } - - mappedCols.push(mappedCol); - } - - return Reo._dedupColoringTable(mappedCols, false).all; - } -}; - -Reo.Connector = function(nodes, channels, components){ - // Reo connector object. - if(!(this instanceof Reo.Connector)) - return; - - this.nodes = {}; - // create nodes - for(var i in nodes){ - var node = nodes[i]; - this.nodes[i] = new Reo.Node(this, i, node.coord, node.type); - } - - this.channels = {}; - // create channels - for(var i in channels){ - var chan = channels[i]; - - // get array of sources (if any) - var sources = chan.sources?chan.sources.map(function(n){ - return this.nodes[n]; - }, this):[]; - - // get array of sinks (if any) - var sinks = chan.sinks?chan.sinks.map(function(n){ - return this.nodes[n]; - }, this):[]; - - this.channels[i] = new Reo.Channel(this, i, sources, sinks, chan.type); - } - - this.components = {}; - this.map = {}; - // create components - for(var i in components){ - var comp = components[i]; - - // determine coord and size - var min = [Infinity, Infinity], - max = [0, 0]; - for(var j in comp.map){ - var node = nodes[comp.map[j]].coord; - min[0] = Math.min(min[0], node[0]); - min[1] = Math.min(min[1], node[1]); - - max[0] = Math.max(max[0], node[0]); - max[1] = Math.max(max[1], node[1]); - } - var coord = [(min[0] + max[0]) / 2, - (min[1] + max[1]) / 2], - size = [max[0] - min[0], - max[1] - min[1]]; - - // create actual connector for each component - var connector = new Reo.Connector(comp.type.nodes, comp.type.channels); - connector.id = comp.type.id; - - this.components[i] = new Reo.Component(this, i, coord, size, comp.map, - connector); - - // create inverse map - for(var j in comp.map){ - if(comp.map[j] in this.map) - throw new Error("Duplicate component mapping"); - this.map[comp.map[j]] = {component: this.components[i], id: j}; - } - } -}; - -Reo.Connector.prototype = { - constructor: Reo.Connector.prototype.constructor, - get colorings(){ - // don't cache colorings - return this._getColorings(); - }, - get coloring(){ - return this._coloring; - }, - set coloring(v){ - this._coloring = v; - this._setColoring(v); - }, - get state(){ - return this._getState(); - }, - set state(v){ - this._setState(v); - } -}; - -Reo.Connector.prototype._getColorings = function(){ - // Returns coloring table for this connector. - if(!this._colorings){ - console.time("Connector._getColorings"); - console.log("Generating cacheable colorings."); - var table = []; - - this._peak = 0; - var queue = []; - // collect I/O nodes - for(var i in this.nodes){ - var node = this.nodes[i]; - if(node.type!="read" && node.type!="write") - continue; - - queue.push(node); - } - - // trace through connector and join tables - for(var node; node = queue.shift();){ - if(node.visited) - continue; - node.visited = true; - - for(var i in node.ends){ - var here = node.ends[i], - chan = here.channel, - there = chan.ends[1 - chan.ends.indexOf(here)].node; - if(chan.visited) - continue; - chan.visited = true; - - if(!there.visited){ - table = Reo._joinColoringTables(table, there.colorings); - this._peak = Math.max(this._peak, table.length); - queue.push(there); - } - - - // these channels don't change type, thus can be cached - if(!chan.type.hasNexts){ - table = Reo._joinColoringTables(table, chan.colorings); - this._peak = Math.max(this._peak, table.length); - } - } - } - - this._colorings = table; - console.timeEnd("Connector._getColorings"); - } - - table = this._colorings; - // these channels may change type, so re-join tables every time - for(var i in this.channels){ - var chan = this.channels[i]; - if(!chan.type.hasNexts) - continue; - table = Reo._joinColoringTables(table, chan.colorings); - } - - // components may change (but are cached internally) - for(var i in this.components){ - var comp = this.components[i]; - table = Reo._joinColoringTables(table, comp.colorings); - } - - return Reo._dedupColoringTable(table, false).all; -}; - -Reo.Connector.prototype._setColoring = function(col){ - // Set the current coloring for this connector. - for(var i in this.nodes){ - var node = this.nodes[i]; - if(col && (!(i in col) || col[i].sources.length!=node.sources.length)) - throw new Error("Incomplete coloring"); - - for(var j in node.sources) - node.sources[j].color = col && col[i].sources[j]; - - for(var j in node.sinks) - node.sinks[j].color = col && col[i].sinks[j]; - } - - // set coloring of components - for(var i in this.components){ - var comp = this.components[i]; - - // filter out component-internal colorings - var connCol = {}; - for(var j in col){ - var node = j.split("/"); - if(node[0]==comp.id) - connCol[node[1]] = col[j]; - } - - comp.connector.coloring = col && connCol; - } -}; - -Reo.Connector.prototype.validColorings = function(io){ - // Return all colorings that are valid with current pending I/O or a given - // [io]. - // XXX: this assumes read and write nodes have only one end - var table = this.colorings; - - for(var i in this.nodes){ - var node = this.nodes[i]; - if(node.type!="read" && node.type!="write") - continue; - - var nodeCols = []; - switch(node.type){ - case "read": - if(io.indexOf(node.sources[0]) >= 0){ - // flow from us - var col = {sources: ["-"], sinks: []}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - }else{ - // flow is blocked by us - var col = {sources: [">"], sinks: []}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - } - - // flow is blocked by connector - { - var col = {sources: ["<"], sinks: []}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - } - break; - case "write": - if(io.indexOf(node.sinks[0]) >= 0){ - // flow to us - var col = {sources: [], sinks: ["-"]}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - }else{ - // flow is blocked by us - var col = {sources: [], sinks: ["<"]}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - } - - // flow is blocked by connector - { - var col = {sources: [], sinks: [">"]}, - tmp = {}; - tmp[node.id] = col; - nodeCols.push(tmp); - } - break; - } - - table = Reo._joinColoringTables(table, nodeCols); - } - - return Reo._dedupColoringTable(table, true); -}; - -Reo.Connector.prototype._getState = function(){ - // Get the state of a connector; that is, the current type of all nextables. - var state = {}; - for(var i in this.channels){ - var chan = this.channels[i]; - if(!chan.type.hasNexts) - continue; - state[i] = chan.type; - } - - for(var i in this.components){ - var comp = this.components[i]; - state[i] = comp.connector.state; - } - - return state; -}; - -Reo.Connector.prototype._setState = function(state){ - // Set the state of a connector. - if(state==null){ - // reset all channel types - for(var i in this.channels){ - var chan = this.channels[i]; - chan.type = chan.typeInit; - } - for(var i in this.components) - this.components[i].connector.state = null; - - return; - } - - for(var i in state){ - if(i in this.components) - this.components[i].connector.state = state[i]; - else if(i in this.channels) - this.channels[i].type = state[i]; - } -}; - -Reo.Connector.prototype.nextTick = function(){ - // Set all channels in this connector to the 'next' state. - - // setting type immediately invalidates connector coloring, - // so collect new types first and set them all afterward - var newType = {}; - - for(var i in this.channels){ - var chan = this.channels[i]; - if(chan.next) - newType[chan.id] = chan.next; - chan.animationType = null; - } - - for(var i in newType){ - this.channels[i].type = newType[i]; - } - - for(var i in this.components){ - var comp = this.components[i]; - comp.connector.nextTick(); - } -}; - -Reo._joinColoringTables = function(tabA, tabB){ - // Returns the join of two coloring tables [tabA] and [tabB]. - var tabC = []; - if(tabA.length==0) - return tabB; - if(tabB.length==0) - return tabA; - - // add all in A - for(var i in tabA){ - for(var j in tabB){ - var joined = Reo._joinColorings(tabA[i], tabB[j]); - if(joined) - tabC.push(joined); - } - } - - return tabC; -}; - -Reo._joinColorings = function(colA, colB){ - // Returns the join of two colorings [colA] and [colB]. - var colC = {}; - - // for each node in A - for(var i in colA){ - // add nodes only in A - if(!(i in colB)){ - colC[i] = colA[i]; - continue; - } - - // check compatible colors - var joinable = true; - var nodeA = colA[i], - nodeB = colB[i], - nodeC = {}; - - // on both source and sink ends - for(var end in nodeA){ - var endB = nodeB[end]; - nodeC[end] = nodeA[end].map(function(n, i){ - // either color is undefined, or the colors match - if(!n || !endB[i] || n==endB[i]) - return n || endB[i]; - - // color mismatch - joinable = false; - }); - - if(!joinable) - return null; - } - colC[i] = nodeC; - } - - // add all nodes only in B - for(var i in colB){ - colC[i] = colC[i] || colB[i]; - } - - return colC; -}; - -Reo._dedupColoringTable = function(tab, ignoreBlame){ - // Remove duplicate entries from a coloring table [tab], and sort by the - // amount of flow-colored ends. Ignores blame direction if [ignoreBlame] is - // true. - // Returns an object of arrays of colorings separated by flow or no-flow, and - // an array of all colorings. - var ret = [], - uniq = {}; - - // determine unique entries - for(var i in tab){ - var col = tab[i]; - - var flow = 0, - mark = []; - for(var j in col){ - var node = col[j], - ends = node.sources.concat(node.sinks); - - // count all flow-marks in this color - flow += ends.reduce(function(ret, n){ - return ret + (n=="-"?1:0); - }, 0); - - var cols = ends.join(","); - if(ignoreBlame) - cols = cols.replace(/[<>]/g, "x"); - mark.push(j + ":" + cols); - } - var marks = mark.sort().join(";"); - if(uniq[marks]) - continue; - - uniq[marks] = true; - ret.push({flow: flow, coloring: col}); - } - - // sort by amount of flow - ret.sort(function(a, b){ - return b.flow - a.flow; - }); - - // return categorized colorings - var cols = {flow: [], noFlow: [], all: []}; - for(var i in ret){ - var col = ret[i]; - if(col.flow > 0) - cols.flow.push(col.coloring); - else - cols.noFlow.push(col.coloring); - cols.all.push(col.coloring); - } - return cols; -}; - -Reo._debug = {}; -Reo._debug.clear = function(){ -}; -Reo._debug.log = function(str){ - console.log(str); -}; - -Reo._debug.dumpColorings = function(tab){ - // Dump coloring table [tab] to console. - if(!(tab instanceof Array)) - tab = [tab]; - - for(var i in tab){ - var node = tab[i]; - console.group(i); - try{ - for(var j in node){ - console.log(j + ": [" + node[j]["sources"] + "]" - + "[" + node[j]["sinks"] + "]"); - } - }finally{ - console.groupEnd(); - } - } -}; diff --git a/reo-online-editor/playground/doc/animation.md b/reo-online-editor/playground/doc/animation.md deleted file mode 100644 index 82be7405..00000000 --- a/reo-online-editor/playground/doc/animation.md +++ /dev/null @@ -1,67 +0,0 @@ -# Animation -A colored connector can be animated; this shows the flow of data items for that -specific coloring. In the past[2], Reo connector animation has been implemented -by writing for each channel and coloring an 'animation specification'. The Reo -Playground takes a significantly simpler approach. - -The Reo.Animation object manages the drawing of the connector and the animation -of a single coloring. - -## Animation steps -An animation of a single coloring consists of several steps. The most important -steps are: - -- `origin` -- `propagateIn` -- `propagateOut` -- `propagateNext` - -In each step, ends that have data items are either one of two lists: the -`pending` list, which contains all ends that can dispense a data item -immediately, and the `blocked` list, which contains all ends that must wait for -another data item to arrive first (such as sink ends on a drain). Whenever a -data item is placed in the `pending` list, it is also marked so that it is not -visited again. - -When the `pending` list is empty, the animation is finished. A callback -function can be attached to the Reo.Animation object to be notified of this. - -### `origin` -In the `origin` step, data items originating from channels (see also the -section "Channel types" in Connectors) are drawn. Simultaneously for each -originating channel, on all flow-colored sink ends, a data item is moved from -the middle of the channel to that sink end. - -After this, all sink ends of nodes at these channel sink ends are pending. -When the data items reach the sink nodes, or if there are no originating -channels, this step finishes and the `propagateIn` step starts. - -### `propagateIn` -In the `propagateIn` step, simultaneously for all pending ends, a data item is -moved from the node at that end into the channel that the end is connected to. -When the data items reach the middle of their channels, this step finishes and -the `propagateOut` step starts. - -### `propagateOut` -In the `propagateOut` step, simultaneously for all pending ends, a data item is -moved from the channel that the end is connected to the node at the *other* -channel end. When the data items reach the node at the other channel end, this -step finishes and the `propagateNext` step starts. - -### `propagateNext` -In the `propagateNext` step, no animation actually takes place. Instead, the -next set of pending and blocked nodes is determined. After this, if there are -pending data items, the `propagateIn` step starts. - -## Channel types during animation -Animations are performed over single colorings. This means that during a single -animation, a connector remains unchanged. However, as mentioned in the section -"Channel types", channels with multiple configurations are implemented as -multiple channel types; during an animation, data items moving around may leave -a channel in an intermediate state (see also the section "Coloring"). - -During animation, a channel can have an 'animation type' applied to it to -handle these intermediate states. When a channel is drawn, the animation type -(if there is one) always takes precedence over the actual type. Note that the -animation type of a channel is purely visual: none of its semantic information -is used. diff --git a/reo-online-editor/playground/doc/connector.md b/reo-online-editor/playground/doc/connector.md deleted file mode 100644 index 63b5a380..00000000 --- a/reo-online-editor/playground/doc/connector.md +++ /dev/null @@ -1,109 +0,0 @@ -# Connectors -A Reo connector consists of nodes and channels, and optionally components. All -nodes and channels have a string identifier, are connected through ends. - -An end connects a single node with a single channel; from the perspective of a -node or a channel, an end is either a source or a sink end. A node's source -ends are a channel's sink ends and vice versa. - -A channel has a user-defined type (see the section "Channel types") and has -exactly 2 ends. These may be either both a source and a sink end, two source -ends, or two sink ends. - -A node has a coordinate, and a type, which is one of: `read`, `write`, `merge`, -`xrouter`, or `edge`. - -A component is a connector and a map of nodes from outside the connector to -inside the connector. - -## Channel types -In Reo, all channels are user-defined. A channel consists of at least an -identifier, a draw function, and a coloring table. An example of a channel -type is given below. - - { - id: "sync", - colorings: [{sources: ["-"], sinks: ["-"]}, - {sources: ["<"], sinks: ["<"]}, - {sources: [">"], sinks: [">"]}], - origin: false, - draw: function(canvas, from, to, mid, angle){ - canvas.drawArrow(from, to); - } - } - -The draw function is called when the channel needs to be drawn. The parameters -give the canvas to draw on, the coordinates where the channel starts (`from`) -and where the channel ends (`to`). For convenience, an array of some useful -points (in fourths) along that line (`mid`), and the angle between `from` and -`to` is given. These can be calculated from the two given coordinates, but -since they are often needed they are passed to the draw function as well. - -The coloring table is an array of objects, each object containing an array for -`sources` and/or an array for `sinks`. Since a channel must have exactly two -ends, if both `sources` and `sinks` are present, each array has only one entry; -if only one is present, that array must have two entries. - -Some channels can be the 'origin' of data items, such as spouts or FIFOs. This -means a channel can emit a data item without it being written to. The property -`origin` must be set to `true` if the channel is an origin. - -Furthermore, channels that can be in multiple 'configurations' or 'states' are -implemented as separate channels altogether. The progression between different -configurations is expressed with the `next`-properties of colorings, described -in the section "Coloring". - -## Components -A connector may contain several components, which are opaque subconnectors. A -component consists of a connector and a map of nodes. The node map is an object -that maps each internal read or write node to a node in the parent connector. - -A component-internal node may be mapped to any parent node, but for performance -reasons, it is recommended that nodes of type `edge` are used. These nodes are -ignored when determining the cached colorings of the parent connector. - -## Coloring -A coloring (see also [1]) is a map from ends to colors. A coloring is -represented as an object with a child for each node in the connector, each -child containing an array of colors for each sink and source end of that node. - -A color is represented as a string, which is one of: `-` (flow), `<` or `>` -(no-flow, pointing towards blame). The direction of blame is defined as such: - -- For a node or channel's source ends, the node or channel (self) is on the - right side and what it connects to is (other) on the left side. The color `>` - blames the self, and the color `<` blames the other. -- For a node or channel's sink ends, the self is on the left side and the other - is on the right side. The color `<` blames the self, and the color `>` blames - the other. - -For channels that can be in multiple configurations (see "Channel types"), a -coloring may optionally have a `next`-property. This property determines which -type the channel becomes if this coloring is applied to it. For example, an -'empty FIFO'-type channel that has its source end flow-colored has a `next` -pointing to a 'full FIFO'-type channel. - -Furthermore, during animation, a channel may be in an intermediate state; -consider a FIFO of capacity 2 containing a single data item (`FIFO2/1`), that -simultaneously originates and receives a data item. After this happens, the -channel unchanged: there is still only 1 data item inside. However, during -animation, there is a moment when the outgoing data item has left the channel -but the incoming has not arrived yet. - -For these intermediate states, a coloring may also have an `in` and an `out` -property. These determine what type the channel becomes when a data item enters -or leaves the channel (respectively). - -A coloring table for a node or channel type is the set of all possible valid -colorings for that node or channel type. The coloring tables for channels are -defined beforehand (see the section "Channel types"). The coloring tables for -nodes are generated based on the number of sources and sinks they have, and -then cached for efficiency. Read and write nodes are not merged into the -coloring table yet, because their coloring depends on the pending I/O. - -The colorings for all nodes and channels are merged to generate a connector -coloring. Connector colorings are partially cached: all nodes and channels -without a `next`-property are merged into an (possibly incomplete) coloring, -which is cached. To generate the complete connector coloring, the coloring -tables for all channels *with* a `next`-property are merged into the cached -coloring. diff --git a/reo-online-editor/playground/doc/references.md b/reo-online-editor/playground/doc/references.md deleted file mode 100644 index 855ea414..00000000 --- a/reo-online-editor/playground/doc/references.md +++ /dev/null @@ -1,6 +0,0 @@ -# References -1. Clarke, D.; Costa, D.; Arbab, F. "Connector colouring I: Synchronisation and - context dependency". Science of Computer Programming, vol. 66, issue 3 - (2007): 205--225. -2. Costa, D. "Formal Models for Component Connectors". Vrije Universiteit - Amsterdam (2010). diff --git a/reo-online-editor/playground/interface.js b/reo-online-editor/playground/interface.js deleted file mode 100644 index e78807b5..00000000 --- a/reo-online-editor/playground/interface.js +++ /dev/null @@ -1,499 +0,0 @@ -"use strict"; - -function $(selector, element){ - return (element || document).querySelectorAll(selector); -} - -window.onerror = function(msg, src, line){ - alert("\"" + msg + "\"\nin " + src.split("/").splice(-1) + ":" + line); -}; - -var Playground = Playground || {}; -Reo && (Reo.debug = false); - -Playground.Playground = function(elements, data, options){ - // Playground object. - if(!(this instanceof Playground.Playground)) - return; - - this.options = options || {}; - this._elements = elements; - - this.animation = new Reo.Animation(elements["animation"]); - this.timeline = new Playground.Timeline(elements["timeline"], this); - - // create index of connectors - for(var i in data.index){ - var li = elements["index"].appendChild(document.createElement("li")), - ul = document.createElement("ul"); - - li.innerText = i; - li.appendChild(ul); - - // connectors - for(var j in data.index[i]){ - var id = data.index[i][j]; - var li = document.createElement("li"), - a = li.appendChild(document.createElement("a")); - - a.innerText = data.connectors[id].name; - a.href = "#" + id; - - ul.appendChild(li); - } - } - - // prepare controls - this._elements["controlPlay"] = $(".play", this._elements["controls"])[0]; - this._elements["controlPlay"].onclick = (function(){ - // Start/stop animation. - if(this.animation.state=="idle") - this.startAnimation(); - else - this.stopAnimation(); - }).bind(this); - - $(".backward", this._elements["controls"])[0].onclick = (function(){ - this.stopAnimation(); - - var tick = Math.max(this.timeline.currentTick - 1, 0); - this.setTick(tick); - }).bind(this); - - $(".forward", this._elements["controls"])[0].onclick = (function(){ - this.stopAnimation(); - - var tick = Math.min(this.timeline.currentTick + 1, - this.timeline.options.maxTicks - 1); - this.setTick(tick); - }).bind(this); - - (window.onhashchange = (function(e){ - // Change the connector to the one given in the hash. - var index = "#" + elements["index"].id; - var a = $(index + " a[href='" + (location.hash || "#") + "']"), - b = $(index + " a[data-selected]"); - b[0] && b[0].removeAttribute("data-selected"); - a[0] && a[0].setAttribute("data-selected", ""); - - // update description - var connector = data.connectors[location.hash.slice(1)]; - if(!connector) - return; - - this.stopAnimation(); - elements["description"].innerHTML = connector.desc; - - // change connector - if(!("nodes" in connector)){ - this.animation.connector = null; - return; - } - - this.animation.connector = new Reo.Connector(connector.nodes, - connector.channels, - connector.components); - - // clean up the timeline - this.timeline.clear(); - this.timeline.nodes = []; - for(var i in this.animation.connector.nodes){ - var node = this.animation.connector.nodes[i]; - if(node.type=="read" || node.type=="write"){ - this.timeline.io.push([]); - this.timeline.nodes.push(node); - } - } - - this.timeline.draw(); - }).bind(this))(); -}; - -Playground.Playground.prototype.setTick = function(tick){ - // Set the current tick to [tick]. - this.timeline.currentTick = tick; - this.timeline.draw(); - - var tick = this.timeline.ticks[tick]; - if(!tick) - return; - - var connector = this.animation.connector; - - connector.state = tick.state; - connector.coloring = tick.coloring; -}; - -Playground.Playground.prototype.simulate = function(from){ - // Simulate the connector according the pending I/O on the timeline, starting - // from tick [from]. - var connector = this.animation.connector; - console.time("Playground.simulate"); - - this.stopAnimation(); - - // reset all channel types - connector.state = null; - - // simulate each tick - var ioLog = []; - for(var i = from; i < this.timeline.options.maxTicks; i++){ - // add all newly pending I/O in this tick - var io = this.timeline.io; - for(var j in io){ - ioLog = ioLog.concat(io[j].filter(function(n){ - return n.from==i; - })); - } - - // remove all fulfilled I/O - // XXX: this assumes read and write nodes have only one end - var ioEnds = ioLog.map(function(n){ - return connector.nodes[n.node].ends[0]; - }, this); - - // get all valid colorings, and separate those with flow - var colorings = connector.validColorings(ioEnds), - cols = (colorings.flow.length > 0)?colorings.flow:colorings.noFlow; - - // choose a coloring - var col = cols[(this.timeline.variants[i] || 0) % cols.length]; - - // check coloring of all ends in this tick and set 'to'-values - var newLog = [], - last = {}; - for(var j in ioEnds){ - if(!col || !(ioLog[j].node in col)) - throw new Error("Incomplete coloring"); - - // XXX: this assumes read and write nodes have only one end - var node = col[ioLog[j].node], - end = node.sources.concat(node.sinks)[0]; - if(ioLog[j].node in last){ - // this event is in the to-range of an earlier event - ioLog[j].node = null; - }else if(end=="-"){ - // this event is fulfilled at this tick - ioLog[j].to = i; - delete last[ioLog[j].node]; - }else{ - ioLog[j].to = Infinity; - newLog.push(ioLog[j]); - } - - // remember the last still pending event for this node - last[ioLog[j].node] = ioLog[j].from; - } - ioLog = newLog; - - // remember coloring and state in this tick - this.timeline.ticks[i] = {io: ioEnds, - coloring: col, - state: connector.state}; - - connector.coloring = col; - connector.nextTick(); - } - - // restore all initial types and reset timeline - connector.state = null; - this.setTick(0); - this.timeline.revalidate(); - - console.timeEnd("Playground.simulate"); -}; - -Playground.Playground.prototype.startAnimation = function(){ - // Start animation. - var tick = this.timeline.ticks[this.timeline.currentTick]; - if(!tick) - return; - - this.animation.io = tick.io; - this.animation.connector.coloring = tick.coloring; - - this.animation.state = "start"; - this._elements["controlPlay"].setAttribute("data-state", "play"); - - this.animation.finishCallback = (function(){ - this._nextTick(); - }).bind(this); -}; - -Playground.Playground.prototype.stopAnimation = function(){ - // Stop animation. - this.animation.state = "idle"; - this._elements["controlPlay"].setAttribute("data-state", "pause"); - - var connector = this.animation.connector; - if(!connector) - return; - - // restore all types - for(var i in connector.channels){ - var chan = connector.channels[i]; - chan.animationType = null; - } -}; - -Playground.Playground.prototype._nextTick = function(){ - // Continue animation with next tick. - this.animation.connector.nextTick(); - - this.timeline.currentTick++; - if(this.timeline.currentTick < this.timeline.options.maxTicks){ - this.startAnimation(); - }else{ - this.stopAnimation(); - this.timeline.currentTick = this.timeline.options.maxTicks - 1; - } - - this.timeline.draw(); -}; - -Playground.Timeline = function(canvas, playground, options){ - // Timeline object. - if(!(this instanceof Playground.Timeline)) - return; - if(!(playground instanceof Playground.Playground)) - throw new TypeError("playground must be Playground.Playground"); - - var opt = options || { - tickWidth: 32, - lineHeight: 16, - lineOffset: 4.5 - }; - opt.nodeOffset = Math.ceil(opt.lineOffset + (opt.lineHeight / 2)); - opt.maxNodes = Math.floor(canvas.height / opt.lineHeight) - 1; - opt.maxTicks = Math.floor(canvas.width / opt.tickWidth) - 1; - - this.options = opt; - - this.canvas = new Canvas(canvas); - - this.nodes = []; - - this.io = []; - this.ticks = []; - this.variants = []; - - this.currentTick = 0; - - // drag event callbacks - this._drag = { - active: false, - node: null, - tick: null, - prev: null, - - begin: (function(x, y){ - // Begin of drag. - var node = Math.floor((y - opt.lineOffset) / opt.lineHeight), - tick = Math.floor(x / opt.tickWidth) - 1; - if(tick < 0 || node < 0 || !this.nodes[node]) - return; - - this._drag.active = true; - canvas.style.cursor = "col-resize"; - - // remove existing tick from this node - var i = this.io[node][tick]; - if(i){ - this._drag.prev = i; - delete this.io[node][tick]; - } - - this._drag.node = node; - this._drag.tick = tick; - this.draw(); - }).bind(this), - - end: (function(){ - // End of drag. - var node = this._drag.node, - tick = this._drag.tick; - if(!this._drag.active) - return; - - this._drag.active = false; - canvas.style.cursor = "default"; - - // make sure we're not adding a bogus node - if(this._validateTick(node, tick)){ - // add tick to this node - var i = this.io[node][tick]; - if(tick!=null && !i) - this.io[node][tick] = {node: this.nodes[node].id, - from: tick, to: -1}; - } - - // re-simulate connector - this.variants[tick] = (this.variants[tick] + 1) || 0; - playground.simulate(0); - - this._drag.node = null; - this._drag.prev = null; - this.draw(); - }).bind(this), - - move: (function(x, y){ - // Pointer movement. - if(!this._drag.active) - return; - - // bars count as 'outside the timeline' - var node = Math.floor((y - opt.lineOffset) / opt.lineHeight), - tick = Math.floor(x / opt.tickWidth); - if(tick==0 || node >= opt.maxNodes) - return drag.leave(); - - this._drag.tick = tick - 1; - this.draw(); - }).bind(this), - - leave: (function(){ - // Pointer leaves timeline. - if(!this._drag.active) - return; - - this._drag.tick = null; - this.draw(); - }).bind(this) - }; - - // attach pointer event handlers - var drag = this._drag; - - canvas.onmousedown = function(e){ - e.preventDefault(); - return drag.begin(e.offsetX, e.offsetY); - }; - canvas.onmousemove = function(e){ - return drag.move(e.offsetX, e.offsetY); - }; - canvas.onmouseout = drag.leave; - document.onmouseup = function(e){ - return drag.end(); - }; - - // attach touch event handlers - canvas.ontouchstart = function(e){ - e.preventDefault(); - return drag.begin(e.targetTouches[0].pageX - canvas.offsetLeft, - e.targetTouches[0].pageY - canvas.offsetTop); - }; - canvas.ontouchmove = function(e){ - var x = e.targetTouches[0].pageX - canvas.offsetLeft, - y = e.targetTouches[0].pageY - canvas.offsetTop; - if(x < 0 || y < 0 || x > canvas.width || y > canvas.height) - return drag.leave(); - - return drag.move(x, y); - }; - canvas.ontouchend = document.onmouseup; - canvas.ontouchleave = drag.leave; -}; - -Playground.Timeline.prototype.clear = function(){ - // Clear all I/O events from the timeline. - this.io = []; - this.ticks = []; - this.currentTick = 0; -}; - -Playground.Timeline.prototype._validateTick = function(node, tick){ - // Returns whether it is possible to place an I/O event for [node] at [tick]. - for(var i in this.io[node]){ - var io = this.io[node][i]; - if(tick >= io.from && tick <= io.to) - return false; - } - return true; -}; - -Playground.Timeline.prototype.revalidate = function(){ - // Remove ticks marked as impossibly placed by simulation. - for(var i in this.io){ - var io = this.io[i]; - for(var j in io){ - if(io[j].node==null) - delete io[j]; - } - } -}; - -Playground.Timeline.prototype.draw = function(){ - // Draw the timeline. - this.canvas.clear(); - - var half = (this.options.tickWidth * 1.5); - var width = this.canvas.canvas.width, - height = this.canvas.canvas.height; - - // draw left bar - this.canvas.drawRect([0, 0], this.options.tickWidth, height, - null, {fill: "#ccc"}); - - var coord = [(this.options.tickWidth / 2), this.options.nodeOffset]; - for(var i in this.nodes){ - this.canvas.drawText(coord, this.nodes[i].id, - {align: "center", baseline: "middle"}); - coord[1] += this.options.lineHeight; - } - - // draw bottom bar - this.canvas.drawRect([-10, height - this.options.lineHeight + 2], - width + 20, this.options.lineHeight + 10, - null, {stroke: "#ccc", fill: "#eee"}); - - // draw all placed I/O - for(var i in this.io){ - var node = this.io[i]; - for(var t in node){ - var io = node[t]; - - var x = (io.from * this.options.tickWidth) + half, - y = (i * this.options.lineHeight) + this.options.nodeOffset; - - var stop = ((0.5 + io.to) * this.options.tickWidth) + half; - if(io.to >= io.from){ - // draw a cross where pending I/O is fulfilled - this.canvas.drawLine([x, y], [stop, y]); - this.canvas.drawLine([stop - 5, y - 5], [stop + 5, y + 5], - {stroke: "#0a0"}); - this.canvas.drawLine([stop + 5, y - 5], [stop - 5, y + 5], - {stroke: "#0a0"}); - } - - this.canvas.drawRect([x, y], 10, 10, {center: true}, - {stroke: "#000", fill: "#fff"}); - } - } - - // draw dragged I/O (if present) - if(this._drag.active && this._drag.tick!=null){ - var x = (this._drag.tick * this.options.tickWidth) + half, - y = (this._drag.node * this.options.lineHeight); - this.canvas.drawRect([x, y + this.options.nodeOffset], 12, 12, - {center: true}, {stroke: "#c00", fill: "#fff"}); - } - - // draw current tick indicator - var x = (this.currentTick * this.options.tickWidth) + half, - y = (this.options.maxNodes + 1) * this.options.lineHeight; - this.canvas.drawPolygon([x, y], {n: 3, radius: 8, angle: -Math.PI / 2}, - {fill: "#000"}); -}; - -var playground = (function(data){ - // Initialize Playground. - var elements = { - "index": $("#indexPanel")[0], - "timeline": $("#timeline")[0], - "controls": $("#controls")[0], - "animation": $("#animation")[0], - "description": $("#description")[0] - }; - - return new Playground.Playground(elements, data); -})(playgroundData); diff --git a/reo-online-editor/playground/playground-data.js b/reo-online-editor/playground/playground-data.js deleted file mode 100644 index 6e210b6f..00000000 --- a/reo-online-editor/playground/playground-data.js +++ /dev/null @@ -1,920 +0,0 @@ -"use strict"; - -var playgroundData = {}; - -// Channel definitions -// XXX: some of these tables include 'flip' entries -playgroundData.channels = { - "sync": { - id: "sync", - colorings: [{sources: ["-"], sinks: ["-"]}, - {sources: ["<"], sinks: ["<"]}, - {sources: [">"], sinks: [">"]}], - draw: function(canvas, from, to){ - canvas.drawArrow(from, to); - } - }, - "lossySync": { - id: "lossySync", - colorings: [{sources: ["-"], sinks: ["-"]}, - {sources: ["-"], sinks: [">"]}, - {sources: ["<"], sinks: ["<"]}, - {sources: ["<"], sinks: [">"]}], - draw: function(canvas, from, to){ - canvas.drawArrow(from, to, {dashed: true}); - } - }, - "syncDrain": { - id: "syncDrain", - colorings: [{sources: ["-", "-"]}, - {sources: ["<", ">"]}, - {sources: [">", "<"]}, - {sources: ["<", "<"]}], - draw: function(canvas, from, to, mid){ - canvas.drawArrow(from, mid[0]); - canvas.drawArrow(to, mid[2]); - canvas.drawLine(mid[0], mid[2]); - } - }, - "asyncDrain": { - id: "asyncDrain", - colorings: [{sources: ["-", ">"]}, - {sources: ["-", "<"]}, - {sources: [">", "-"]}, - {sources: ["<", "-"]}, - {sources: ["<", "<"]}], - draw: function(canvas, from, to, mid, ang){ - canvas.drawArrow(from, mid[0]); - canvas.drawArrow(to, mid[2]); - canvas.drawLine(mid[0], mid[2]); - - // middle dashes - var xo = Math.sin(ang) * 3, - yo = Math.cos(ang) * 3; - - canvas.drawPolygon([mid[1][0] + xo, mid[1][1] + yo], - {n: 2, radius: 8, angle: -ang}); - canvas.drawPolygon([mid[1][0] - xo, mid[1][1] - yo], - {n: 2, radius: 8, angle: -ang}); - } - }, - "syncSpout": { - id: "syncSpout", - colorings: [{sinks: ["-", "-"]}, - {sinks: ["<", ">"]}, - {sinks: [">", "<"]}, - {sinks: [">", ">"]}], - origin: true, - draw: function(canvas, from, to, mid){ - canvas.drawArrow(mid[0], from); - canvas.drawArrow(mid[0], to); - } - }, - "asyncSpout": { - id: "asyncSpout", - colorings: [{sinks: ["-", "<"]}, - {sinks: ["-", ">"]}, - {sinks: ["<", "-"]}, - {sinks: [">", "-"]}, - {sinks: [">", ">"]}], - origin: true, - draw: function(canvas, from, to, mid, ang){ - canvas.drawArrow(mid[0], from); - canvas.drawArrow(mid[0], to); - - // middle dashes - var xo = Math.sin(ang) * 3, - yo = Math.cos(ang) * 3; - - canvas.drawPolygon([mid[1][0] + xo, mid[1][1] + yo], - {n: 2, radius: 8, angle: -ang}); - canvas.drawPolygon([mid[1][0] - xo, mid[1][1] - yo], - {n: 2, radius: 8, angle: -ang}); - } - }, - "filter": { - id: "filter", - colorings: [{sources: ["-"], sinks: ["-"]}], - draw: function(canvas, from, to, mid, ang){ - var top = Math.PI / 2 + ang; - var points = [from, - [mid[1][0] + 27 * Math.sin(top + (3/6) * Math.PI), - mid[1][1] + 27 * Math.cos(top + (3/6) * Math.PI)], - [mid[1][0] + 25 * Math.sin(top + (2/6) * Math.PI), - mid[1][1] + 25 * Math.cos(top + (2/6) * Math.PI)], - [mid[1][0] + 20 * Math.sin(top + (5/6) * Math.PI), - mid[1][1] + 20 * Math.cos(top + (5/6) * Math.PI)], - [mid[1][0] + 13 * Math.sin(top), - mid[1][1] + 13 * Math.cos(top)], - [mid[1][0] + 20 * Math.sin(top - (5/6) * Math.PI), - mid[1][1] + 20 * Math.cos(top - (5/6) * Math.PI)], - [mid[1][0] + 25 * Math.sin(top - (2/6) * Math.PI), - mid[1][1] + 25 * Math.cos(top - (2/6) * Math.PI)], - [mid[1][0] + 27 * Math.sin(top - (3/6) * Math.PI), - mid[1][1] + 27 * Math.cos(top - (3/6) * Math.PI)], - to]; - canvas.drawPath(points); - canvas.drawArrow(mid[2], to); - } - } -}; - -playgroundData._generateFIFO = function(K){ - // Generate channel data for a FIFO with capacity [K]. - var fifo = []; - - for(var I = 0; I <= K; I++){ - var chan = {}; - - chan.id = "fifo" + K + "/" + I; - chan.draw = (function(K, I){ - return function(canvas, from, to, mid, ang){ - playgroundData._drawFIFO(canvas, from, to, mid, ang, K, I); - }; - })(K, I); - - // data items can originate from filled FIFOs - chan.origin = (I > 0); - - // XXX: some of these tables include 'flip' entries - if(I==0) - chan.colorings = [["-", "<"], ["<", "<"], - ["-", ">"], ["<", ">"]]; - else if(I==K) - chan.colorings = [[">", "-"], [">", ">"], - ["<", "-"], ["<", ">"]]; - else - chan.colorings = [["-", "-"], ["-", "<"], [">", "-"], ["<", ">"], - ["<", "-"]]; - - chan.colorings.forEach(function(n, i){ - this[i] = {sources: [n[0]], sinks: [n[1]]}; - - // determine how many items after this coloring - if(n[0]=="-" && n[1]!="-") - this[i].next = I + 1; - else if(n[0]!="-" && n[1]=="-") - this[i].next = I - 1; - - }, chan.colorings); - - chan.hasNexts = true; - fifo.push(chan); - } - - // convert amount of items to channel reference - for(var i in fifo){ - fifo[i].colorings.forEach(function(n, i){ - n.next = fifo[n.next]; - }); - - fifo[i].in = fifo[Number(i) + 1]; - fifo[i].out = fifo[i - 1]; - } - - return fifo; -}; - -playgroundData._drawFIFO = function(canvas, from, to, mid, ang, K, I){ - // Draw a generic FIFO with capacity [K] and filled with [I] items. - if(I > K) - throw new Error("FIFO cannot exceed capacity"); - - canvas.drawArrow(from, to); - canvas.drawRect(mid[1], 20, 46, - {angle: -ang, center: true}, {stroke: "#000", fill: "#fff"}); - - // FIFO capacity - if(K > 1){ - var capacity = [mid[1][0] + (Math.cos(ang) * 20), - mid[1][1] - (Math.sin(ang) * 20)]; - canvas.drawRect(capacity, 20, 20, - {angle: -ang, center: true}, {fill: "#000"}); - canvas.drawText(capacity, K, - {align: "center", baseline: "middle"}, - {fill: "#fff"}); - } - - // amount of items - if(I > 0) - canvas.drawPolygon(mid[1], {n: 5, radius: 8, angle: -Math.PI / 2}, - {fill: "#fc0"}); - if(I > 1) - canvas.drawText(mid[1], I, {align: "center", baseline: "middle"}); -}; - -(function(channels, K){ - // Add FIFO channel definitions up to capacity [K]. - for(var i = 1; i <= K; i++){ - playgroundData._generateFIFO(i).forEach(function(n){ - channels[n.id] = n; - }); - } -})(playgroundData.channels, 3); - -// Component definitions -playgroundData.components = { - "inhibitor": { - id: "inhibitor", - nodes: { - "A": {coord: [160, 140], type: "write"}, - "B": {coord: [480, 140], type: "read"}, - "c": {coord: [320, 140], type: "merge"}, - - "I": {coord: [160, 240], type: "write"}, - - "d": {coord: [320, 240], type: "merge"}, - "e": {coord: [240, 240], type: "merge"}, - "f": {coord: [320, 340], type: "merge"}, - "g": {coord: [400, 240], type: "merge"} - }, - channels: { - "Ac": {sources: ["A"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cB": {sources: ["c"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - - "cd": {sources: ["c", "d"], - type: playgroundData.channels["syncDrain"]}, - - "de": {sources: ["d"], sinks: ["e"], - type: playgroundData.channels["sync"]}, - "ef": {sources: ["e"], sinks: ["f"], - type: playgroundData.channels["fifo1/0"]}, - "fg": {sources: ["f"], sinks: ["g"], - type: playgroundData.channels["fifo1/1"]}, - "gd": {sources: ["g"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - - "Ie": {sources: ["I"], sinks: ["e"], - type: playgroundData.channels["sync"]} - } - }, - "lossyChooser": { - id: "lossyChooser", - nodes: { - "A": {coord: [120, 140], type: "write"}, - "B": {coord: [120, 340], type: "write"}, - "d": {coord: [240, 140], type: "merge"}, - "e": {coord: [240, 340], type: "merge"}, - "f": {coord: [400, 240], type: "merge"}, - "C": {coord: [520, 240], type: "read"} - }, - channels: { - "Ad": {sources: ["A"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - "Be": {sources: ["B"], sinks: ["e"], - type: playgroundData.channels["sync"]}, - "de": {sources: ["d", "e"], - type: playgroundData.channels["syncDrain"]}, - "df": {sources: ["d"], sinks: ["f"], - type: playgroundData.channels["sync"]}, - "ef": {sources: ["e"], sinks: ["f"], - type: playgroundData.channels["lossySync"]}, - "fC": {sources: ["f"], sinks: ["C"], - type: playgroundData.channels["sync"]} - } - } -}; - -// Connector definitions -playgroundData.connectors = { - "": { - desc: "

Welcome to the Reo Playground!

" + - "

Reo is channel-" + - "based exogenous coordination model in which complex " + - "coordinators, called connectors, are compositionally " + - "built out of simpler ones. The simplest connectors in Reo are " + - "a set of user-defined channels." + - "

This application visualizes various Reo connectors under " + - "given I/O conditions. Use the timeline below the animation to " + - "control when I/O arrives.

" + - "
  • To add an event, click on the " + - "timeline.
  • " + - "
  • To change the timing of an event, drag it " + - "to the desired position.
  • " + - "
  • To remove an event, drag it off the " + - "timeline.
" + - "

Previous version: " + - "WebReo" + - "

", - nodes: { - "b": {coord: [160, 160], type: "merge"}, - "c": {coord: [320, 240], type: "merge"}, - "d": {coord: [160, 320], type: "merge"}, - "R": {coord: [480, 240], type: "read"}, - }, - channels: { - "bc": {sources: ["b"], sinks: ["c"], - type: playgroundData.channels["fifo1/1"]}, - "cd": {sources: ["c"], sinks: ["d"], - type: playgroundData.channels["fifo1/0"]}, - "db": {sources: ["d"], sinks: ["b"], - type: playgroundData.channels["sync"]}, - "cR": {sources: ["c"], sinks: ["R"], - type: playgroundData.channels["sync"]} - } - }, - "sync": { - name: "Sync", - desc: "A synchronous channel accepts a data item from a writer " + - "at the source end if it can be atomically transferred to a " + - "reader at the sink end.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["sync"]} - } - }, - "lossySync": { - name: "Lossy sync", - desc: "A lossy synchronous channel always accepts a data item " + - "from the source end. If there simultaneously is a reader at the " + - "sink end, the data is atomically transferred, otherwise it is " + - "lost.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["lossySync"]} - } - }, - "syncDrain": { - name: "Sync drain", - desc: "A synchronous drain accepts a data item from both of its " + - "source ends simultaneously; if there is a write pending to only " + - "one of the two source ends, it will block. The data written to " + - "a drain is lost.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "write"} - }, - channels: { - "AB": {sources: ["A", "B"], - type: playgroundData.channels["syncDrain"]} - } - }, - "asyncDrain": { - name: "Async drain", - desc: "An asynchronous drain accepts a data item from one of its " + - "source ends and loses it. If there are writes pending at both " + - "source ends, they will never happen simultaneously: the write " + - "that succeeds is nondeterministically chosen.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "write"} - }, - channels: { - "AB": {sources: ["A", "B"], - type: playgroundData.channels["asyncDrain"]} - } - }, - "syncSpout": { - name: "Sync spout", - desc: "A synchronous spout dispenses an arbitrary data item from " + - "both of its sink ends simultaneously; if there is a read pending " + - "from only one of the two sink ends, it will block.", - nodes: { - "A": {coord: [200, 240], type: "read"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sinks: ["A", "B"], - type: playgroundData.channels["syncSpout"]} - } - }, - "asyncSpout": { - name: "Async spout", - desc: "An asynchronous spout dispenses an arbitrary data item " + - "from one of its sink ends. If there are writes pending at both " + - "sink ends, they will never happen simultaneously: the read that " + - "succeeds is nondeterministically chosen.", - nodes: { - "A": {coord: [200, 240], type: "read"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sinks: ["A", "B"], - type: playgroundData.channels["asyncSpout"]} - } - }, - "fifo1": { - name: "FIFO-1", - desc: "A FIFO-1 channel is a queue with a buffer of capacity 1. " + - "A read at its sink end only succeeds if the buffer is full. " + - "Similarly, a write at the source end only succeeds if the buffer " + - "is empty.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["fifo1/0"]} - } - }, - "fifok": { - name: "FIFO-k", - desc: "A FIFO-k channel is a queue with a buffer of " + - "capacity k. In this example, k=3.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["fifo3/0"]} - } - }, - "filter": { - name: "Filter", - desc: "A filter channel transfers data items that match a filter " + - "pattern. Data items that match the pattern will be transferred " + - "like a synchronous channel. Data items " + - "that do not match the pattern are accepted, but are immediately " + - "lost.", - nodes: { - "A": {coord: [200, 240], type: "write"}, - "B": {coord: [440, 240], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["filter"]} - } - }, - "xrouter": { - name: "Exclusive router", - desc: "An exclusive router is a connector that transfers " + - "incoming data items at A to either sink end B or C, but not " + - "both. If there is only one read pending, it will receive the " + - "incoming data item. If there are multiple reads pending, one " + - "will be nondeterministically chosen to receive the incoming data " + - "item.", - nodes: { - "A": {coord: [120, 240], type: "write"}, - "d": {coord: [240, 240], type: "merge"}, - "e": {coord: [400, 140], type: "merge"}, - "f": {coord: [400, 340], type: "merge"}, - "g": {coord: [400, 240], type: "merge"}, - "B": {coord: [520, 140], type: "read"}, - "C": {coord: [520, 340], type: "read"} - }, - channels: { - "Ad": {sources: ["A"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - "de": {sources: ["d"], sinks: ["e"], - type: playgroundData.channels["lossySync"]}, - "df": {sources: ["d"], sinks: ["f"], - type: playgroundData.channels["lossySync"]}, - "dg": {sources: ["d", "g"], - type: playgroundData.channels["syncDrain"]}, - "eg": {sources: ["e"], sinks: ["g"], - type: playgroundData.channels["sync"]}, - "fg": {sources: ["f"], sinks: ["g"], - type: playgroundData.channels["sync"]}, - "eB": {sources: ["e"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "fC": {sources: ["f"], sinks: ["C"], - type: playgroundData.channels["sync"]} - } - }, - "xrouterNode": { - name: "Exclusive router node", - desc: "The exclusive router node is a shorthand notation for the " + - "exclusive router. It can also be " + - "generalized to have more than 2 sink ends.", - nodes: { - "A": {coord: [120, 240], type: "write"}, - "d": {coord: [320, 240], type: "xrouter"}, - "B": {coord: [520, 140], type: "read"}, - "C": {coord: [520, 240], type: "read"}, - "D": {coord: [520, 340], type: "read"} - }, - channels: { - "Ad": {sources: ["A"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - "dB": {sources: ["d"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "dC": {sources: ["d"], sinks: ["C"], - type: playgroundData.channels["sync"]}, - "dD": {sources: ["d"], sinks: ["D"], - type: playgroundData.channels["sync"]} - } - }, - "takeRegulator": { - name: "Take-cue regulator", - desc: "

The take-cue regulator allows data flow from A to B " + - "only when there is a read at R.

" + - "

Using this connector, the flow between A and B is regulated " + - "by R — even though none of the actors were designed to " + - "exhibit such behavior! The coordination is completely determined " + - "by the connector.

", - nodes: { - "A": {coord: [160, 190], type: "write"}, - "B": {coord: [480, 190], type: "read"}, - "c": {coord: [320, 190], type: "merge"}, - "R": {coord: [320, 290], type: "read"} - }, - channels: { - "Ac": {sources: ["A"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cB": {sources: ["c"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "cR": {sources: ["c"], sinks: ["R"], - type: playgroundData.channels["sync"]} - } - }, - "writeRegulator": { - name: "Write-cue regulator", - desc: "The write-cue regulator allows data flow from A to B only " + - "when there is a write at R.", - nodes: { - "A": {coord: [160, 190], type: "write"}, - "B": {coord: [480, 190], type: "read"}, - "c": {coord: [320, 190], type: "merge"}, - "R": {coord: [320, 290], type: "write"} - }, - channels: { - "Ac": {sources: ["A"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cB": {sources: ["c"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "cR": {sources: ["c", "R"], - type: playgroundData.channels["syncDrain"]} - } - }, - "barrierSync": { - name: "Barrier synchronizer", - desc: "

The barrier synchronizer lets data flow from A to C " + - "only when it is also possible for data to flow from B to D. The " + - "reads C and D succeed simultaneously, regardless of when writes " + - "at A and B arrive.

" + - "

This connector can be seen as an extension of the " + - "write-cue regulator.

", - nodes: { - "A": {coord: [160, 190], type: "write"}, - "B": {coord: [480, 190], type: "read"}, - "e": {coord: [320, 190], type: "merge"}, - "f": {coord: [320, 290], type: "merge"}, - "C": {coord: [160, 290], type: "write"}, - "D": {coord: [480, 290], type: "read"} - }, - channels: { - "Ae": {sources: ["A"], sinks: ["e"], - type: playgroundData.channels["sync"]}, - "eB": {sources: ["e"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "ef": {sources: ["e", "f"], - type: playgroundData.channels["syncDrain"]}, - "Cf": {sources: ["C"], sinks: ["f"], - type: playgroundData.channels["sync"]}, - "fD": {sources: ["f"], sinks: ["D"], - type: playgroundData.channels["sync"]} - } - }, - "feedbackLoop": { - name: "Feedback loop", - desc: "A feedback loop provides a data item on demand. Whenever " + - "a read appears on A, it receives a predetermined data item.", - nodes: { - "b": {coord: [160, 160], type: "merge"}, - "c": {coord: [320, 240], type: "merge"}, - "d": {coord: [160, 320], type: "merge"}, - "A": {coord: [480, 240], type: "read"} - }, - channels: { - "bc": {sources: ["b"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cd": {sources: ["c"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - "db": {sources: ["d"], sinks: ["b"], - type: playgroundData.channels["fifo2/1"]}, - "cA": {sources: ["c"], sinks: ["A"], - type: playgroundData.channels["sync"]} - } - }, - "sequencer": { - name: "Sequencer", - desc: "A sequencer only permits reads to succeed in order. Any " + - "read will block until all preceding reads have succeeded.", - nodes: { - "A": {coord: [250, 90], type: "read"}, - "B": {coord: [390, 90], type: "read"}, - "C": {coord: [520, 90], type: "read"}, - - "d": {coord: [120, 290], type: "merge"}, - "e": {coord: [250, 240], type: "merge"}, - "f": {coord: [390, 240], type: "merge"}, - "g": {coord: [520, 290], type: "merge"}, - - "h": {coord: [320, 390], type: "merge"} - }, - channels: { - "eA": {sources: ["e"], sinks: ["A"], - type: playgroundData.channels["sync"]}, - "fB": {sources: ["f"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "gC": {sources: ["g"], sinks: ["C"], - type: playgroundData.channels["sync"]}, - - "de": {sources: ["d"], sinks: ["e"], - type: playgroundData.channels["fifo1/1"]}, - "ef": {sources: ["e"], sinks: ["f"], - type: playgroundData.channels["fifo1/0"]}, - "fg": {sources: ["f"], sinks: ["g"], - type: playgroundData.channels["fifo1/0"]}, - - "gh": {sources: ["g"], sinks: ["h"], - type: playgroundData.channels["sync"]}, - "hd": {sources: ["h"], sinks: ["d"], - type: playgroundData.channels["sync"]} - } - }, - "inhibitor": { - name: "Inhibitor", - desc: "An inhibitor allows the connection between A and B to be " + - "'closed' with a write on I, prohibiting any further communication.", - nodes: playgroundData.components["inhibitor"].nodes, - channels: playgroundData.components["inhibitor"].channels, - }, - "orSelector": { - name: "Or-selector", - desc: "An or-selector allows only one of the two writers to " + - "succeed and will inhibit any further " + - "writes from the other writer. When two writers appear " + - "simultaneously, one is nondeterministically chosen.", - nodes: { - // I/O - "A": {coord: [100, 150], type: "write"}, - "a": {coord: [160, 150], type: "merge"}, - "B": {coord: [100, 330], type: "write"}, - "b": {coord: [160, 330], type: "merge"}, - "C": {coord: [540, 240], type: "read"}, - "c": {coord: [480, 240], type: "merge"}, - - // Inhibitor 1 - "i1A": {coord: [250, 120], type: "edge"}, - "i1I": {coord: [250, 180], type: "edge"}, - "i1B": {coord: [390, 150], type: "edge"}, - - // Inhibitor 2 - "i2A": {coord: [250, 360], type: "edge"}, - "i2I": {coord: [250, 300], type: "edge"}, - "i2B": {coord: [390, 330], type: "edge"} - }, - channels: { - "Aa": {sources: ["A"], sinks: ["a"], - type: playgroundData.channels["sync"]}, - "Bb": {sources: ["B"], sinks: ["b"], - type: playgroundData.channels["sync"]}, - "cC": {sources: ["c"], sinks: ["C"], - type: playgroundData.channels["sync"]}, - - "aiA": {sources: ["a"], sinks: ["i1A"], - type: playgroundData.channels["sync"]}, - "aiI": {sources: ["a"], sinks: ["i2I"], - type: playgroundData.channels["lossySync"]}, - - "biA": {sources: ["b"], sinks: ["i2A"], - type: playgroundData.channels["sync"]}, - "biI": {sources: ["b"], sinks: ["i1I"], - type: playgroundData.channels["lossySync"]}, - - "i1c": {sources: ["i1B"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "i2c": {sources: ["i2B"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - }, - components: { - "i1": {map: {"A": "i1A", "I": "i1I", "B": "i1B"}, - type: playgroundData.components["inhibitor"]}, - "i2": {map: {"A": "i2A", "I": "i2I", "B": "i2B"}, - type: playgroundData.components["inhibitor"]} - } - }, - "valve": { - name: "Valve", - desc: "A valve is like an inhibitor, " + - "in that it allows 'closing' the communication between A and B, " + - "with a write on I, but unlike an inhibitor, the valve allows the " + - "communication to be 'opened' again with another write on I.", - nodes: { - "A": {coord: [100, 140], type: "write"}, - "B": {coord: [540, 140], type: "read"}, - "c": {coord: [400, 140], type: "merge"}, - - "I": {coord: [100, 240], type: "write"}, - - "d": {coord: [400, 240], type: "merge"}, - "e": {coord: [320, 240], type: "merge"}, - "f": {coord: [400, 360], type: "xrouter"}, - "g": {coord: [480, 240], type: "merge"}, - - "h": {coord: [240, 190], type: "merge"}, - "i": {coord: [240, 290], type: "merge"}, - "j": {coord: [160, 240], type: "xrouter"}, - "k": {coord: [160, 360], type: "merge"}, - }, - channels: { - "Ac": {sources: ["A"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cB": {sources: ["c"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - - "cd": {sources: ["c", "d"], - type: playgroundData.channels["syncDrain"]}, - - "de": {sources: ["d"], sinks: ["e"], - type: playgroundData.channels["sync"]}, - "ef": {sources: ["e"], sinks: ["f"], - type: playgroundData.channels["fifo1/0"]}, - "fg": {sources: ["f"], sinks: ["g"], - type: playgroundData.channels["fifo1/1"]}, - "gd": {sources: ["g"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - - "fk": {sources: ["f", "k"], - type: playgroundData.channels["syncDrain"]}, - - "Ij": {sources: ["I"], sinks: ["j"], - type: playgroundData.channels["sync"]}, - "jk": {sources: ["j"], sinks: ["k"], - type: playgroundData.channels["sync"]}, - "jh": {sources: ["j"], sinks: ["h"], - type: playgroundData.channels["sync"]}, - "ki": {sources: ["k"], sinks: ["i"], - type: playgroundData.channels["fifo1/1"]}, - - "ih": {sources: ["i", "h"], - type: playgroundData.channels["syncDrain"]}, - "he": {sources: ["h"], sinks: ["e"], - type: playgroundData.channels["sync"]} - } - }, - "discriminator": { - name: "Discriminator", - desc: "The discriminator takes the first of its inputs and " + - "transfers it to the output. Before the next output can be sent, " + - "all other writers must arrive; their data will be discarded.", - nodes: { - "A": {coord: [160, 100], type: "write"}, - "a": {coord: [250, 100], type: "merge"}, - "c": {coord: [480, 100], type: "merge"}, - - "B": {coord: [160, 180], type: "write"}, - "b": {coord: [250, 180], type: "merge"}, - "d": {coord: [480, 180], type: "merge"}, - - "f": {coord: [320, 300], type: "merge"}, - "e": {coord: [480, 300], type: "merge"}, - - "g": {coord: [250, 380], type: "merge"}, - "h": {coord: [480, 380], type: "merge"}, - "I": {coord: [160, 380], type: "read"} - }, - channels: { - "Aa": {sources: ["A"], sinks: ["a"], - type: playgroundData.channels["sync"]}, - "Bb": {sources: ["B"], sinks: ["b"], - type: playgroundData.channels["sync"]}, - - "af": {sources: ["a"], sinks: ["f"], - type: playgroundData.channels["lossySync"]}, - "bf": {sources: ["b"], sinks: ["f"], - type: playgroundData.channels["lossySync"]}, - - "ac": {sources: ["a"], sinks: ["c"], - type: playgroundData.channels["fifo1/0"]}, - "bd": {sources: ["b"], sinks: ["d"], - type: playgroundData.channels["fifo1/0"]}, - - "fe": {sources: ["f"], sinks: ["e"], - type: playgroundData.channels["fifo1/0"]}, - "fg": {sources: ["f"], sinks: ["g"], - type: playgroundData.channels["fifo1/0"]}, - "gh": {sources: ["g"], sinks: ["h"], - type: playgroundData.channels["fifo1/0"]}, - - "cd": {sources: ["c", "d"], - type: playgroundData.channels["syncDrain"]}, - "de": {sources: ["d", "e"], - type: playgroundData.channels["syncDrain"]}, - "eh": {sources: ["e", "h"], - type: playgroundData.channels["syncDrain"]}, - - "gI": {sources: ["g"], sinks: ["I"], - type: playgroundData.channels["sync"]} - } - }, - "shiftFifo1": { - name: "Shift-lossy FIFO-1", - desc: "A shift-lossy FIFO-1 acts like a " + - "FIFO-1, except that when it is full, " + - "writes still succeed: the old contents are lost to make space " + - "for new incoming data items.", - nodes: { - "A": {coord: [100, 190], type: "write"}, - "c": {coord: [200, 190], type: "merge"}, - "d": {coord: [440, 190], type: "xrouter"}, - "e": {coord: [200, 290], type: "merge"}, - "f": {coord: [440, 290], type: "merge"}, - "B": {coord: [540, 290], type: "read"} - }, - channels: { - "Ac": {sources: ["A"], sinks: ["c"], - type: playgroundData.channels["sync"]}, - "cd": {sources: ["c"], sinks: ["d"], - type: playgroundData.channels["fifo2/0"]}, - "de": {sources: ["d"], sinks: ["e"], - type: playgroundData.channels["sync"]}, - "fe": {sources: ["f"], sinks: ["e"], - type: playgroundData.channels["fifo1/1"]}, - "ec": {sources: ["e", "c"], - type: playgroundData.channels["syncDrain"]}, - "df": {sources: ["d"], sinks: ["f"], - type: playgroundData.channels["sync"]}, - "fB": {sources: ["f"], sinks: ["B"], - type: playgroundData.channels["sync"]} - } - }, - // debug connectors - "debug-read": { - name: "2 writers at read", - desc: "This is a debug connector, used for testing the simulation.", - nodes: { - "A": {coord: [100, 150], type: "write"}, - "B": {coord: [100, 330], type: "write"}, - "C": {coord: [540, 240], type: "read"} - }, - channels: { - "AC": {sources: ["A"], sinks: ["C"], - type: playgroundData.channels["sync"]}, - "BC": {sources: ["B"], sinks: ["C"], - type: playgroundData.channels["sync"]} - } - }, - "debug-write": { - name: "2 readers at write", - desc: "This is a debug connector, used for testing the simulation.", - nodes: { - "A": {coord: [100, 240], type: "write"}, - "B": {coord: [540, 140], type: "read"}, - "C": {coord: [540, 330], type: "read"} - }, - channels: { - "AB": {sources: ["A"], sinks: ["B"], - type: playgroundData.channels["sync"]}, - "AC": {sources: ["A"], sinks: ["C"], - type: playgroundData.channels["sync"]} - } - }, - "debug-lossyChooser": { - name: "Lossy chooser", - desc: "This is a debug connector, used for testing the simulation.", - nodes: playgroundData.components["lossyChooser"].nodes, - channels: playgroundData.components["lossyChooser"].channels, - }, - "debug-component": { - name: "Flawed animation", - desc: "This is a debug connector, used for testing the simulation.", - nodes: { - "A": {coord: [100, 150], type: "write"}, - "B": {coord: [100, 330], type: "write"}, - "C": {coord: [540, 240], type: "read"}, - "d": {coord: [175, 180], type: "merge"}, - - "iA": {coord: [250, 210], type: "edge"}, - "iB": {coord: [250, 270], type: "edge"}, - "iC": {coord: [390, 240], type: "edge"} - }, - channels: { - "Ad": {sources: ["A"], sinks: ["d"], - type: playgroundData.channels["sync"]}, - "diA": {sources: ["d"], sinks: ["iA"], - type: playgroundData.channels["sync"]}, - "BiB": {sources: ["B"], sinks: ["iB"], - type: playgroundData.channels["sync"]}, - "CiC": {sources: ["iC"], sinks: ["C"], - type: playgroundData.channels["sync"]} - }, - components: { - "i": {map: {"A": "iA", "B": "iB", "C": "iC"}, - type: playgroundData.components["lossyChooser"]} - } - } -}; - -// Playground index -playgroundData.index = { - "Single channels": ["sync", "lossySync", "syncDrain", "asyncDrain", - "syncSpout", "asyncSpout", "fifo1", "fifok"], - "Connectors": ["takeRegulator", "writeRegulator", "barrierSync", - "feedbackLoop", "shiftFifo1", "xrouter", "xrouterNode", - "sequencer", "inhibitor", "orSelector", "valve", - "discriminator"] -}; diff --git a/reo-online-editor/playground/playground.css b/reo-online-editor/playground/playground.css deleted file mode 100644 index f543562c..00000000 --- a/reo-online-editor/playground/playground.css +++ /dev/null @@ -1,131 +0,0 @@ -*{ - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; -} - -html{ - height: 100%; -} - -body{ - height: 100%; - background: #eee; -} - -canvas{ - display: block; - box-sizing: content-box; - margin: .5em auto; - - background: #eee; - border: 1px solid #ccc; - border-radius: 2px; -} -canvas > .unsupported{ - display: block; - margin: 25% 7em; - text-align: center; -} - -h1{ - margin: 0 -.5em .5em -.5em; - font-size: 1.5em; - text-align: center; -} - -p{ - margin-top: 0; -} - -a{ - color: #03f; - font-weight: normal; -} -a[data-selected]{ - color: #000; - cursor: default; - text-decoration: none; - font-weight: bold; -} - -#content{ - width: 100%; - min-height: 100%; - margin: auto; - padding: 1em; - background: #fff; -} -/* pure-u-lg */ -@media (min-width: 64em){ - #content{ - width: 90%; - padding: 1.5em; - } -} - -#indexPanel, #indexPanel ul{ - list-style-position: inside; - padding-left: 0em; -} -#indexPanel{ - list-style-type: none; - border-right: 2px solid #000; - padding: 0; - margin: 0; -} -#indexPanel > li{ - padding-bottom: 1em; - font-weight: bold; -} - -#animationPanel{ - width: 650px; - margin: .5em auto; -} - -#animation{ - background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAG0lEQVR42mM4c+bMfwYCgBg1o4BIQExggtQAANoaDY2fVJg1AAAAAElFTkSuQmCC"); -} -#timeline{ - display: inline-block; - vertical-align: middle; - margin: 0; - background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAQCAYAAADXnxW3AAAAE0lEQVR42mNgIBKcOXPmP4TACwDsyQbHTurJBQAAAABJRU5ErkJggg==") 0px 4px, - url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAABCAYAAABubagXAAAAEklEQVR42mO4e/fu/4HEDAMMAEkLcsETaNQwAAAAAElFTkSuQmCC"); - background-color: #eee; -} - -#controls{ - display: block; - vertical-align: middle; - text-align: center; -} -#controlButtons{ - display: inline-block; - width: 64px; - vertical-align: middle; - text-align: right; - line-height: 0; -} -#controlButtons button{ - width: 80%; - margin: 1px 0; - padding: 0.25em; -} -#controlButtons svg{ - display: block; - margin: auto; -} -#controlButtons .play[data-state="play"] svg:first-child, -#controlButtons .play[data-state="pause"] svg:last-child{ - display: none; -} - -#description{ - padding: .5em; -} -#description ul{ - margin: .5em 0; - padding: 0 0 0 1em; -} diff --git a/reo-online-editor/playground/playground.html b/reo-online-editor/playground/playground.html deleted file mode 100644 index 91dfe776..00000000 --- a/reo-online-editor/playground/playground.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Reo Playground - - - - - - - - - - -
- - - -
-
-
- - - Your browser does not support or has disabled the <canvas> element, which is required for - this application. - -
- -
- -
-
-
-
-
- -
- - - - diff --git a/reo-online-editor/rect.html b/reo-online-editor/rect.html deleted file mode 100644 index c973e659..00000000 --- a/reo-online-editor/rect.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - Online Reo editor - - - -
-
- - -
-
- - - - - -
SelectNew component
- -
-
-

Channels

-
- - - - -
- Sync
- Sync -
-
-
- Please use a browser that supports canvas. -
-
- - - - \ No newline at end of file diff --git a/reo-online-editor/rect.js b/reo-online-editor/rect.js deleted file mode 100644 index 70ff1ff1..00000000 --- a/reo-online-editor/rect.js +++ /dev/null @@ -1,198 +0,0 @@ -(function() { - var canvas = this.__canvas = new fabric.Canvas('c', { selection: false }); - fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'; - var active, isDown, origX, origY, origLeft, origTop; - var mode = 'select'; - var id = '0'; - - document.getElementById("select").onclick = function() { - document.getElementById(mode).style.border = '2px solid white'; - mode = 'select'; - this.style.border = '2px solid black'; - }; - - document.getElementById("component").onclick = function() { - document.getElementById(mode).style.border = '2px solid white'; - mode = 'component'; - this.style.border = '2px solid black'; - }; - - document.getElementById("sync").onclick = function() { - document.getElementById(mode).style.border = '2px solid white'; - mode = 'sync'; - this.style.border = '2px solid black'; - }; - - document.getElementById("downloadsvg").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.svg"; - a.href = 'data:image/svg+xml;base64,' + window.btoa(canvas.toSVG()); - a.click(); - }; - - document.getElementById("downloadpng").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.png"; - a.href = canvas.toDataURL('image/png'); - a.click(); - }; - - function generateId() { - id = ((parseInt(id, 36)+1).toString(36)).replace(/[0-9]/g,'a'); - return id; - } - - function updateText() { - if (main) { - var s1 = main.label.text + '('; - var s2 = ''; - var space1 = space2 = ''; - - canvas.forEachObject(function(obj) { - if (obj.class == 'node') { - if (obj.left == main.left || obj.top == main.top || obj.left == main.left + main.width || obj.top == main.top + main.height) { - s1 += space1 + obj.label.text; - space1 = ', ' - - } - } - if (obj.class == 'channel') - s2 += space2 + 'sync(' + obj.circle1.label.text + ',' + obj.circle2.label.text + ')'; - space2 = ' '; - }); - - s1 = s1 + ') {\n ' + s2 + '\n}'; - document.getElementById('text').innerHTML = s1; - } - } - - canvas.on('object:moving', function(e) { - var p = e.target; - p.setCoords(); - }); //object:moving - - canvas.on('object:added', function(e) { - updateText(); - }); //object:added - - canvas.on('object:removed', function(e) { - updateText(); - }); //object:removed - - canvas.on('text:changed', function(e) { - updateText(); - }); //text:editing:exited - - canvas.on('mouse:down', function(e) { - isDown = true; - var pointer = canvas.getPointer(e.e); - origX = pointer.x; - origY = pointer.y; - var p = canvas.getActiveObject(); - console.log(p); - if (p) { - origLeft = p.left; - origTop = p.top; - return; - } - if (mode == 'component') { - var comp = drawComponent(pointer.x,pointer.y,pointer.x,pointer.y); - canvas.setActiveObject(comp); - } - }); //mouse:down - - canvas.on('mouse:move', function(e){ - if (!isDown) - return; - var p = canvas.getActiveObject(); - if (!p) - return; - var pointer = canvas.getPointer(e.e); - if (p.class == 'component') { - if (p.status == 'drawing') { - if (origX > pointer.x) - p.set({left:pointer.x}); - if (origY > pointer.y) - p.set({top:pointer.y}); - p.set({width:Math.abs(origX - pointer.x)}); - p.set({height:Math.abs(origY - pointer.y)}); - p.setCoords(); - p.label.set({left: p.left + (p.width/2), top: p.top - 15}); - p.label.setCoords(); - } - if (p.status == 'design') { - p.set({left: origLeft + pointer.x - origX}); - p.set({top: origTop + pointer.y - origY}); - p.setCoords(); - p.label.set({left: p.left + p.labelOffsetX}); - p.label.set({top: p.top + p.labelOffsetY}); - p.label.setCoords(); - } - } - canvas.renderAll(); - }); //mouse:move - - canvas.on('mouse:up', function(e){ - isDown = false; - var p = canvas.getActiveObject(); - if (p) { - p.setCoords(); - if (p.class == 'component') { - p.label.setCoords(); - p.set({'labelOffsetX': p.label.left - p.left, 'labelOffsetY': p.label.top - p.top, status: 'design'}); - } - if (p.class == 'label') { - p.object.set({'labelOffsetX': p.left - p.object.left, 'labelOffsetY': p.top - p.object.top}); - } - } - }); - - function drawComponent(x1,y1,x2,y2) { - var width = (x2 - x1); - var height = (y2 - y1); - var left = x1; - var top = y1; - - var rect = new fabric.Rect({ - left: left, - top: top, - width: width, - height, height, - size: width * height, - fill: 'transparent', - stroke: '#000', - strokeWidth: 1, - hoverCursor: 'default', - originX: 'left', - originY: 'top', - //hasBorders: false, - //hasControls: false, - class: 'component', - status: 'drawing', - id: generateId() - }); - - var label = new fabric.IText('name', { - left: left + (width / 2), - top: top - 15, - fontSize: 32, - class: 'label', - object: rect, - hasControls: false - }); - - rect.set({'label': label, 'labelOffsetX': left + (width / 2), 'labelOffsetY': -15}); - - rect.setCoords(); - canvas.add(rect,label); - canvas.renderAll(); - return rect; - } - - var main = drawComponent(50,50,750,550); - main.set({id: 'main'}); - main.label.set({'text': 'main'}); - id = '0'; - document.getElementById("component").click(); - -})(); \ No newline at end of file diff --git a/reo-online-editor/transform.html b/reo-online-editor/transform.html deleted file mode 100644 index 217dd5b0..00000000 --- a/reo-online-editor/transform.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - Transform test - - - -
-
- - -
-
- - - - - -
SelectNew component
- -
-
-

Channels

- - - - - - - - - - - - - - - - - -
- Sync
- Sync -
- Lossy sync
- Lossy sync -
- Sync drain
- Sync drain -
- Sync spout
- Sync spout -
- FIFO1
- FIFO1 -
-
-
- Please use a browser that supports canvas. -
-
- - - - - \ No newline at end of file diff --git a/reo-online-editor/transform.js b/reo-online-editor/transform.js deleted file mode 100644 index 85a4db4e..00000000 --- a/reo-online-editor/transform.js +++ /dev/null @@ -1,479 +0,0 @@ -(function() { - - var canvas = this.__canvas = new fabric.Canvas('c', { selection: false }); - - fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'; - - fabric.Object.prototype.objectCaching = false; - var active, isDown, origX, origY, origLeft, origTop; - var mode = 'select'; - var id = '0'; - var nodes = []; - - // drawing parameters - - nodeFillColourSource = '#fff'; - nodeFillColourDrain = '#fff'; - nodeFillColourMixed = '#000'; - nodeFactor = 4; - - lineFillColour = '#000'; - lineStrokeColour = '#000'; - lineStrokeWidth = 1; - - arrowFactor = 8; - arrowOffsetOut = lineStrokeWidth * nodeFactor + 4; - arrowOffsetIn = arrowOffsetOut + arrowFactor; - - fifoHeight = 30; - fifoWidth = 10; - fifoFillColour = '#fff'; - - buttonBorderOff = '2px solid white'; - buttonBorderOn = '2px solid black'; - - document.getElementById("select").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'select'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': true}); - } - }); - }; - - document.getElementById("component").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'component'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("sync").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'sync'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("lossysync").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'lossysync'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("syncdrain").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'syncdrain'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("syncspout").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'syncspout'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("fifo1").onclick = function() { - document.getElementById(mode).style.border = buttonBorderOff; - mode = 'fifo1'; - this.style.border = buttonBorderOn; - canvas.forEachObject(function(obj) { - if (obj.class === 'component') { - obj.set({'selectable': false}); - } - }); - }; - - document.getElementById("downloadsvg").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.svg"; - a.href = 'data:image/svg+xml;base64,' + window.btoa(canvas.toSVG()); - a.click(); - }; - - document.getElementById("downloadpng").onclick = function () { - var a = document.getElementById("download"); - a.download = "reo.png"; - a.href = canvas.toDataURL('image/png'); - a.click(); - }; - - // generate a new object ID - // ID will only contain letters, e.g. z is followed by aa - function generateId() { - id = ((parseInt(id, 36)+1).toString(36)).replace(/[0-9]/g,'a'); - return id; - } - - function createNode(left, top) { - var node = new fabric.Circle({ - left: left, - top: top, - angle: 90, - strokeWidth: lineStrokeWidth, - fill: nodeFillColourSource, - radius: nodeFactor * lineStrokeWidth, - stroke: lineStrokeColour, - hasControls: false, - selectable: true, - class: 'node', - id: generateId() - }); - - // these are the channels that are connected to this node - node.channels = []; - - var label = new fabric.IText(node.id, { - left: left + 20, - top: top - 20, - fontSize: 20, - object: node, - class: 'label', - hasControls: false - //visible: false - }); - - node.set({'label': label, 'labelOffsetX': 20, 'labelOffsetY': -20}); - - label.on('editing:exited', function(e) { - label.object.set({id: label.text}); - }); - - nodes.push(node); - - return node; - - } //createNode - - function createAnchor(left, top) { - var anchor = new fabric.Circle({ - left: left, - top: top, - strokeWidth: lineStrokeWidth, - radius: nodeFactor * lineStrokeWidth, - stroke: lineStrokeColour, - hasControls: false, - class: 'anchor', - opacity: 0 - }); - return anchor; - - } //createAnchor - - function createChannel(name, x1, y1, x2, y2) { - // create a channel... - var channel = { - class: 'channel', - components: [] - }; - - var diffX = Math.abs(x1-x2); - var diffY = Math.abs(y1-y2); - - // ...a reference rectangle... - channel.components[0] = new fabric.Rect({ - width: 5, - height: 100, - baseLength: 100, - left: Math.min(x1,x2) + diffX / 2, - top: Math.min(y1,y2) + diffY / 2, - angle: 90, - fill: 'red', - visible: false, - selectable: false, - originX: 'center', - originY: 'center', - }); - - // ...two nodes... - channel.node1 = createNode(x1,y1); - channel.node2 = createNode(x2,y2); - - // ...and two anchors - // TODO - channel.anchor1 = createAnchor(133,100); - channel.anchor2 = createAnchor(167,100); - - // link the channel to the nodes - channel.node1.channels.push(channel); - channel.node2.channels.push(channel); - - // currently loaded from a separate file - // TODO: replace with a database search - switch(name) { - case 'sync': - createSync(channel,x1,y1,x2,y2); - break; - case 'lossysync': - createLossySync(channel,x1,y1,x2,y2); - break; - case 'syncdrain': - createSyncDrain(channel,x1,y1,x2,y2); - break; - case 'syncspout': - createSyncSpout(channel,x1,y1,x2,y2); - break; - case 'fifo1': - createFIFO1(channel,x1,y1,x2,y2); - break; - default: - console.log("Invalid channel name"); - return; - break; - } - - canvas.add(channel.components[0]); - for (i = 1; i < channel.components.length; i++) { - var o = channel.components[i]; - var bossTransform = channel.components[0].calcTransformMatrix(); - var invertedBossTransform = fabric.util.invertTransform(bossTransform); - var desiredTransform = fabric.util.multiplyTransformMatrices(invertedBossTransform, channel.components[i].calcTransformMatrix()); - o.relationship = desiredTransform; - canvas.add(channel.components[i]); - } - canvas.add(channel.node1, channel.node2, channel.node1.label, channel.node2.label, channel.anchor1, channel.anchor2); - //updateChannel(channel); - - return channel; - } //createChannel - - function calculateAngle(channel, baseAngle) { - var angle = 0; - var x = (channel.node2.get('left') - channel.node1.get('left')); - var y = (channel.node2.get('top') - channel.node1.get('top')); - - if (x === 0) { - angle = (y === 0) ? 0 : (y > 0) ? Math.PI / 2 : Math.PI * 3 / 2; - } else if (y === 0) { - angle = (x > 0) ? 0 : Math.PI; - } else { - angle = (x < 0) ? Math.atan(y / x) + Math.PI : (y < 0) ? Math.atan(y / x) + (2 * Math.PI) : Math.atan(y / x); - } - - return ((angle * 180 / Math.PI) + baseAngle) % 360; - } //calculateAngle - - function updateChannel(channel) { - var x1 = channel.node1.get('left'); - var y1 = channel.node1.get('top'); - var x2 = channel.node2.get('left'); - var y2 = channel.node2.get('top'); - var diffX = Math.abs(x1-x2); - var diffY = Math.abs(y1-y2); - - // update the reference rectangle - channel.components[0].set({'left': Math.min(x1,x2) + diffX / 2}); - channel.components[0].set({'top': Math.min(y1,y2) + diffY / 2}); - channel.components[0].set({'angle': calculateAngle(channel, 90)}); - - // convert new size to scaling - var length = Math.sqrt(Math.pow(x1-x2,2) + Math.pow(y1-y2,2)); - var scale = length/channel.components[0].baseLength; - channel.components[0].set({'scaleX': scale, 'scaleY': scale}); - - channel.components[0].setCoords(); - - // update all channel components - for (k = 1; k < channel.components.length; k++) { - var o = channel.components[k]; - if (o.type == 'line') { - o.set({'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2}); - } - else { - if (!o.relationship) { - console.log("No relationship found"); - return; - } - var relationship = o.relationship; - var newTransform = fabric.util.multiplyTransformMatrices(channel.components[0].calcTransformMatrix(), relationship); - opt = fabric.util.qrDecompose(newTransform); - o.set({ - flipX: false, - flipY: false, - }); - o.setPositionByOrigin( - { x: opt.translateX, y: opt.translateY }, - 'center', - 'center', - ); - o.set(opt); - if (o.scale == false) - o.set({'scaleX': 1, 'scaleY': 1}); - if (o.rotate == false) - o.set({'angle': o.baseAngle}); - if (o.referencePoint == 'node1') { - o.set({ - 'left': o.referenceDistance * Math.cos((channel.components[0].angle + o.referenceAngle + 180) * Math.PI / 180) + channel.node1.left, - 'top': o.referenceDistance * Math.sin((channel.components[0].angle + o.referenceAngle + 180) * Math.PI / 180) + channel.node1.top - }); - } - if (o.referencePoint == 'node2') { - o.set({ - 'left': o.referenceDistance * Math.cos((channel.components[0].angle + o.referenceAngle + 180) * Math.PI / 180) + channel.node2.left, - 'top': o.referenceDistance * Math.sin((channel.components[0].angle + o.referenceAngle + 180) * Math.PI / 180) + channel.node2.top - }); - } - } - o.setCoords(); - } - canvas.renderAll(); - } //updateChannel - - canvas.on('object:moving', function(e) { - e.target.setCoords(); - }); //object:moving - - canvas.on('mouse:over', function(e) { - if (e.target && e.target.class == "anchor") - { - e.target.set('opacity', '100'); - canvas.renderAll(); - } - }); //mouse:over - - canvas.on('mouse:out', function(e) { - if (e.target && e.target.class == "anchor") - { - e.target.set('opacity', '0'); - canvas.renderAll(); - } - }); //mouse:out - - canvas.on('mouse:down', function(e) { - isDown = true; - var pointer = canvas.getPointer(e.e); - origX = pointer.x; - origY = pointer.y; - var p = canvas.getActiveObject(); - if (p && mode != 'select') { - origLeft = p.left; - origTop = p.top; - return; - } - if (mode == 'select') { - //console.log('Mode is select'); - } - if (mode == 'sync' || mode == 'lossysync' || mode == 'syncdrain' || mode == 'syncspout' || mode == 'fifo1') { - canvas.discardActiveObject(); - var channel = createChannel(mode,pointer.x,pointer.y,pointer.x,pointer.y); - canvas.setActiveObject(channel.node2); - } - }); //mouse:down - - canvas.on('mouse:move', function(e){ - if (!isDown) - return; - var p = canvas.getActiveObject(); - if (!p) - return; - var pointer = canvas.getPointer(e.e); - if (p.class == 'node') { - p.set({'left': pointer.x, 'top': pointer.y}); - p.setCoords(); - canvas.forEachObject(function(obj) { - if (obj !== p && p.intersectsWithObject(obj)) { - if (obj.class === 'node') { - if (Math.abs(p.left-obj.left) < 10 && Math.abs(p.top-obj.top) < 10) { - p.set({'left': obj.left, 'top': obj.top}); - p.setCoords(); - } - } - } - }); - - for (i = 0; i < p.channels.length; i++) - updateChannel(p.channels[i]); - - p.label.set({left: p.left + p.labelOffsetX}); - p.label.set({top: p.top + p.labelOffsetY}); - p.label.setCoords(); - } - if (p.class == 'group') { - p.set({left: origLeft + pointer.x - origX}); - p.set({top: origTop + pointer.y - origY}); - p.setCoords(); - p.label.set({left: p.left + p.labelOffsetX}); - p.label.set({top: p.top + p.labelOffsetY}); - p.label.setCoords(); - } - canvas.renderAll(); - }); //mouse:move - - canvas.on('mouse:up', function(e){ - isDown = false; - var p = canvas.getActiveObject(); - if (p) { - p.setCoords(); - if (p.class == 'node') { - p.label.setCoords(); - p.set({labelOffsetX: p.label.left - p.left, labelOffsetY: p.label.top - p.top}); - - for (i = nodes.length - 1; i >= 0; i--) { - if (nodes[i].id == p.id) - continue; - if (p.intersectsWithObject(nodes[i])) { - if(Math.abs(p.left-nodes[i].left) < 10 && Math.abs(p.top-nodes[i].top) < 10) { - for (j = 0; j < nodes[i].channels.length; j++) { - if (nodes[i].channels[j].node1.id == nodes[i].id) { - nodes[i].channels[j].node1 = p; - } - else { - if (nodes[i].channels[j].node2.id == nodes[i].id) - nodes[i].channels[j].node2 = p; - else - console.log("Error merging nodes"); - } - p.channels.push(nodes[i].channels[j]); - } - canvas.remove(nodes[i].label, nodes[i]); - nodes.splice(i,1); - p.bringToFront(); - } - } - } - canvas.renderAll(); - canvas.calcOffset(); - } - if (p.class == 'label') { - p.setCoords(); - p.object.set({'labelOffsetX': p.left - p.object.left, 'labelOffsetY': p.top - p.object.top}); - } - else - canvas.discardActiveObject(); - canvas.renderAll(); - } - }); //mouse:up - - id = '0'; - document.getElementById("select").click(); - createChannel('sync',100,100,200,100); - createChannel('lossysync',100,200,200,200); - createChannel('syncdrain',100,300,200,300); - createChannel('syncspout',100,400,200,400); - createChannel('fifo1',100,500,200,500); -})();