/***@licensejCanvasv20.1.3*Copyright2017CalebEvans*ReleasedundertheMITlicense*/(function (jQuery, global, base_factory) { 'use strict'; if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = function (jQuery, w) { return base_factory(jQuery, w); }; } else { base_factory(jQuery, global); }//Passthisifwindowisnotdefinedyet}(typeofwindow!=='undefined'?window.jQuery:{},typeofwindow!=='undefined'?window:this,function($,window){'use strict';vardocument=window.document,Image=window.Image,Array=window.Array,getComputedStyle=window.getComputedStyle,Math=window.Math,Number=window.Number,parseFloat=window.parseFloat;// Define local aliases to frequently used propertiesvardefaults,// Aliases to jQuery methodsextendObject=$.extend,inArray=$.inArray,typeOf=function(operand){returnObject.prototype.toString.call(operand).slice(8,-1).toLowerCase();},isFunction=$.isFunction,isPlainObject=$.isPlainObject,// Math constants and functionsPI=Math.PI,round=Math.round,abs=Math.abs,sin=Math.sin,cos=Math.cos,atan2=Math.atan2,// The Array slice() methodarraySlice=Array.prototype.slice,// jQuery's internal event normalization functionjQueryEventFix=$.event.fix,// Object for storing a number of internal property mapsmaps={},// jQuery internal cachescaches={dataCache:{},propCache:{},imageCache:{}},// Base transformationsbaseTransforms={rotate:0,scaleX:1,scaleY:1,translateX:0,translateY:0,// Store all previous masksmasks:[]},// Object for storing CSS-related propertiescss={},tangibleEvents=['mousedown','mousemove','mouseup','mouseover','mouseout','touchstart','touchmove','touchend'];// Constructor for creating objects that inherit from jCanvas preferences and defaultsfunctionjCanvasObject(args){varparams=this,propName;// Copy the given parameters into new objectfor(propNameinargs){// Do not merge defaults into parametersif(Object.prototype.hasOwnProperty.call(args,propName)){params[propName]=args[propName];}}returnparams;}// jCanvas object in which global settings are other data are storedvarjCanvas={// Events object for storing jCanvas event initiation functionsevents:{},// Object containing all jCanvas event hookseventHooks:{},// Settings for enabling future jCanvas featuresfuture:{}};// jCanvas default property valuesfunctionjCanvasDefaults(){extendObject(this,jCanvasDefaults.baseDefaults);}jCanvasDefaults.baseDefaults={align:'center',arrowAngle:90,arrowRadius:0,autosave:true,baseline:'middle',bringToFront:false,ccw:false,closed:false,compositing:'source-over',concavity:0,cornerRadius:0,count:1,cropFromCenter:true,crossOrigin:null,cursors:null,disableEvents:false,draggable:false,dragGroups:null,groups:null,data:null,dx:null,dy:null,end:360,eventX:null,eventY:null,fillStyle:'transparent',fontStyle:'normal',fontSize:'12pt',fontFamily:'sans-serif',fromCenter:true,height:null,imageSmoothing:true,inDegrees:true,intangible:false,index:null,letterSpacing:null,lineHeight:1,layer:false,mask:false,maxWidth:null,miterLimit:10,name:null,opacity:1,r1:null,r2:null,radius:0,repeat:'repeat',respectAlign:false,restrictDragToAxis:null,rotate:0,rounded:false,scale:1,scaleX:1,scaleY:1,shadowBlur:0,shadowColor:'transparent',shadowStroke:false,shadowX:0,shadowY:0,sHeight:null,sides:0,source:'',spread:0,start:0,strokeCap:'butt',strokeDash:null,strokeDashOffset:0,strokeJoin:'miter',strokeStyle:'transparent',strokeWidth:1,sWidth:null,sx:null,sy:null,text:'',translate:0,translateX:0,translateY:0,type:null,visible:true,width:null,x:0,y:0};defaults=newjCanvasDefaults();jCanvasObject.prototype=defaults;/* Internal helper methods */// Determines if the given operand is a stringfunctionisString(operand){return(typeOf(operand)==='string');}// Determines if the given operand is numericfunctionisNumeric(operand){return!isNaN(Number(operand))&&!isNaN(parseFloat(operand));}// Get 2D context for the given canvasfunction_getContext(canvas){return(canvas&&canvas.getContext?canvas.getContext('2d'):null);}// Coerce designated number properties from strings to numbersfunction_coerceNumericProps(props){varpropName,propType,propValue;// Loop through all properties in given property mapfor(propNameinprops){if(Object.prototype.hasOwnProperty.call(props,propName)){propValue=props[propName];propType=typeOf(propValue);// If property is non-empty string and value is numericif(propType==='string'&&isNumeric(propValue)&&propName!=='text'){// Convert value to numberprops[propName]=parseFloat(propValue);}}}// Ensure value of text property is always a stringif(props.text!==undefined){props.text=String(props.text);}}// Clone the given transformations objectfunction_cloneTransforms(transforms){// Clone the object itselftransforms=extendObject({},transforms);// Clone the object's masks arraytransforms.masks=transforms.masks.slice(0);returntransforms;}// Save canvas context and update transformation stackfunction_saveCanvas(ctx,data){vartransforms;ctx.save();transforms=_cloneTransforms(data.transforms);data.savedTransforms.push(transforms);}// Restore canvas context update transformation stackfunction_restoreCanvas(ctx,data){if(data.savedTransforms.length===0){// Reset transformation state if it can't be restored any moredata.transforms=_cloneTransforms(baseTransforms);}else{// Restore canvas contextctx.restore();// Restore current transform state to the last saved statedata.transforms=data.savedTransforms.pop();}}// Set the style with the given namefunction_setStyle(canvas,ctx,params,styleName){if(params[styleName]){if(isFunction(params[styleName])){// Handle functionsctx[styleName]=params[styleName].call(canvas,params);}else{// Handle string valuesctx[styleName]=params[styleName];}}}// Set canvas context propertiesfunction_setGlobalProps(canvas,ctx,params){_setStyle(canvas,ctx,params,'fillStyle');_setStyle(canvas,ctx,params,'strokeStyle');ctx.lineWidth=params.strokeWidth;// Optionally round corners for pathsif(params.rounded){ctx.lineCap=ctx.lineJoin='round';}else{ctx.lineCap=params.strokeCap;ctx.lineJoin=params.strokeJoin;ctx.miterLimit=params.miterLimit;}// Reset strokeDash if nullif(!params.strokeDash){params.strokeDash=[];}// Dashed linesif(ctx.setLineDash){ctx.setLineDash(params.strokeDash);}ctx.webkitLineDash=params.strokeDash;ctx.lineDashOffset=ctx.webkitLineDashOffset=ctx.mozDashOffset=params.strokeDashOffset;// Drop shadowctx.shadowOffsetX=params.shadowX;ctx.shadowOffsetY=params.shadowY;ctx.shadowBlur=params.shadowBlur;ctx.shadowColor=params.shadowColor;// Opacity and composite operationctx.globalAlpha=params.opacity;ctx.globalCompositeOperation=params.compositing;// Support cross-browser toggling of image smoothingif(params.imageSmoothing){ctx.imageSmoothingEnabled=params.imageSmoothing;}}// Optionally enable masking support for this pathfunction_enableMasking(ctx,data,params){if(params.mask){// If jCanvas autosave is enabledif(params.autosave){// Automatically save transformation state by default_saveCanvas(ctx,data);}// Clip the current pathctx.clip();// Keep track of current masksdata.transforms.masks.push(params._args);}}// Restore individual shape transformationfunction_restoreTransform(ctx,params){// If shape has been transformed by jCanvasif(params._transformed){// Restore canvas contextctx.restore();}}// Close current canvas pathfunction_closePath(canvas,ctx,params){vardata;// Optionally close pathif(params.closed){ctx.closePath();}if(params.shadowStroke&¶ms.strokeWidth!==0){// Extend the shadow to include the stroke of a drawing// Add a stroke shadow by stroking before fillingctx.stroke();ctx.fill();// Ensure the below stroking does not inherit a shadowctx.shadowColor='transparent';ctx.shadowBlur=0;// Stroke over fill as usualctx.stroke();}else{// If shadowStroke is not enabled, stroke & fill as usualctx.fill();// Prevent extra shadow created by stroke (but only when fill is present)if(params.fillStyle!=='transparent'){ctx.shadowColor='transparent';}if(params.strokeWidth!==0){// Only stroke if the stroke is not 0ctx.stroke();}}// Optionally close pathif(!params.closed){ctx.closePath();}// Restore individual shape transformation_restoreTransform(ctx,params);// Mask shape if chosenif(params.mask){// Retrieve canvas datadata=_getCanvasData(canvas);_enableMasking(ctx,data,params);}}// Transform (translate, scale, or rotate) shapefunction_transformShape(canvas,ctx,params,width,height){// Get conversion factor for radiansparams._toRad=(params.inDegrees?(PI/180):1);params._transformed=true;ctx.save();// Optionally measure (x, y) position from top-left cornerif(!params.fromCenter&&!params._centered&&width!==undefined){// Always draw from center unless otherwise specifiedif(height===undefined){height=width;}params.x+=width/2;params.y+=height/2;params._centered=true;}// Optionally rotate shapeif(params.rotate){_rotateCanvas(ctx,params,null);}// Optionally scale shapeif(params.scale!==1||params.scaleX!==1||params.scaleY!==1){_scaleCanvas(ctx,params,null);}// Optionally translate shapeif(params.translate||params.translateX||params.translateY){_translateCanvas(ctx,params,null);}}/* Plugin API */// Extend jCanvas with a user-defined methodjCanvas.extend=functionextend(plugin){// Create pluginif(plugin.name){// Merge properties with defaultsif(plugin.props){extendObject(defaults,plugin.props);}// Define plugin method$.fn[plugin.name]=functionself(args){var$canvases=this,canvas,e,ctx,params;for(e=0;e<$canvases.length;e+=1){canvas=$canvases[e];ctx=_getContext(canvas);if(ctx){params=newjCanvasObject(args);_addLayer(canvas,params,args,self);_setGlobalProps(canvas,ctx,params);plugin.fn.call(canvas,ctx,params);}}return$canvases;};// Add drawing type to drawing mapif(plugin.type){maps.drawings[plugin.type]=plugin.name;}}return$.fn[plugin.name];};/* Layer API */// Retrieved the stored jCanvas data for a canvas elementfunction_getCanvasData(canvas){vardataCache=caches.dataCache,data;if(dataCache._canvas===canvas&&dataCache._data){// Retrieve canvas data from cache if possibledata=dataCache._data;}else{// Retrieve canvas data from jQuery's internal data storagedata=$.data(canvas,'jCanvas');if(!data){// Create canvas data object if it does not already existdata={// The associated canvas elementcanvas:canvas,// Layers arraylayers:[],// Layer mapslayer:{names:{},groups:{}},eventHooks:{},// All layers that intersect with the event coordinates (regardless of visibility)intersecting:[],// The topmost layer whose area contains the event coordinateslastIntersected:null,cursor:$(canvas).css('cursor'),// Properties for the current drag eventdrag:{layer:null,dragging:false},// Data for the current eventevent:{type:null,x:null,y:null},// Events which already have been bound to the canvasevents:{},// The canvas's current transformation statetransforms:_cloneTransforms(baseTransforms),savedTransforms:[],// Whether a layer is being animated or notanimating:false,// The layer currently being animatedanimated:null,// The device pixel ratiopixelRatio:1,// Whether pixel ratio transformations have been appliedscaled:false,// Whether the canvas should be redrawn when a layer mousemove// event triggers (either directly, or indirectly via dragging)redrawOnMousemove:false};// Use jQuery to store canvas data$.data(canvas,'jCanvas',data);}// Cache canvas data for faster retrievaldataCache._canvas=canvas;dataCache._data=data;}returndata;}// Initialize all of a layer's associated jCanvas eventsfunction_addLayerEvents($canvas,data,layer){vareventName;// Determine which jCanvas events need to be bound to this layerfor(eventNameinjCanvas.events){if(Object.prototype.hasOwnProperty.call(jCanvas.events,eventName)){// If layer has callback function to complement itif(layer[eventName]||(layer.cursors&&layer.cursors[eventName])){// Bind event to layer_addExplicitLayerEvent($canvas,data,layer,eventName);}}}if(!data.events.mouseout){$canvas.bind('mouseout.jCanvas',function(){// Retrieve the layer whose drag event was canceledvarlayer=data.drag.layer,l;// If cursor mouses out of canvas while draggingif(layer){// Cancel dragdata.drag={};_triggerLayerEvent($canvas,data,layer,'dragcancel');}// Loop through all layersfor(l=0;l<data.layers.length;l+=1){layer=data.layers[l];// If layer thinks it's still being moused overif(layer._hovered){// Trigger mouseout on layer$canvas.triggerLayerEvent(data.layers[l],'mouseout');}}// Redraw layers$canvas.drawLayers();});// Indicate that an event handler has been bounddata.events.mouseout=true;}}// Initialize the given event on the given layerfunction_addLayerEvent($canvas,data,layer,eventName){// Use touch events if appropriate// eventName = _getMouseEventName(eventName);// Bind event to layerjCanvas.events[eventName]($canvas,data);layer._event=true;}// Add a layer event that was explicitly declared in the layer's parameter map,// excluding events added implicitly (e.g. mousemove event required by draggable// layers)function_addExplicitLayerEvent($canvas,data,layer,eventName){_addLayerEvent($canvas,data,layer,eventName);if(eventName==='mouseover'||eventName==='mouseout'||eventName==='mousemove'){data.redrawOnMousemove=true;}}// Enable drag support for this layerfunction_enableDrag($canvas,data,layer){vardragHelperEvents,eventName,i;// Only make layer draggable if necessaryif(layer.draggable||layer.cursors){// Organize helper events which enable drag supportdragHelperEvents=['mousedown','mousemove','mouseup'];// Bind each helper event to the canvasfor(i=0;i<dragHelperEvents.length;i+=1){// Use touch events if appropriateeventName=dragHelperEvents[i];// Bind event_addLayerEvent($canvas,data,layer,eventName);}// Indicate that this layer has events bound to itlayer._event=true;}}// Update a layer property map if property is changedfunction_updateLayerName($canvas,data,layer,props){varnameMap=data.layer.names;// If layer name is being added, not changedif(!props){props=layer;}else{// Remove old layer name entry because layer name has changedif(props.name!==undefined&&isString(layer.name)&&layer.name!==props.name){deletenameMap[layer.name];}}// Add new entry to layer name map with new nameif(isString(props.name)){nameMap[props.name]=layer;}}// Create or update the data map for the given layer and group typefunction_updateLayerGroups($canvas,data,layer,props){vargroupMap=data.layer.groups,group,groupName,g,index,l;// If group name is not changingif(!props){props=layer;}else{// Remove layer from all of its associated groupsif(props.groups!==undefined&&layer.groups!==null){for(g=0;g<layer.groups.length;g+=1){groupName=layer.groups[g];group=groupMap[groupName];if(group){// Remove layer from its old layer group entryfor(l=0;l<group.length;l+=1){if(group[l]===layer){// Keep track of the layer's initial indexindex=l;// Remove layer once foundgroup.splice(l,1);break;}}// Remove layer group entry if group is emptyif(group.length===0){deletegroupMap[groupName];}}}}}// Add layer to new group if a new group name is givenif(props.groups!==undefined&&props.groups!==null){for(g=0;g<props.groups.length;g+=1){groupName=props.groups[g];group=groupMap[groupName];if(!group){// Create new group entry if it doesn't existgroup=groupMap[groupName]=[];group.name=groupName;}if(index===undefined){// Add layer to end of group unless otherwise statedindex=group.length;}// Add layer to its new layer groupgroup.splice(index,0,layer);}}}// Get event hooks object for the first selected canvas$.fn.getEventHooks=functiongetEventHooks(){var$canvases=this,canvas,data,eventHooks={};if($canvases.length!==0){canvas=$canvases[0];data=_getCanvasData(canvas);eventHooks=data.eventHooks;}returneventHooks;};// Set event hooks for the selected canvases$.fn.setEventHooks=functionsetEventHooks(eventHooks){var$canvases=this,e,data;for(e=0;e<$canvases.length;e+=1){data=_getCanvasData($canvases[e]);extendObject(data.eventHooks,eventHooks);}return$canvases;};// Get jCanvas layers array$.fn.getLayers=functiongetLayers(callback){var$canvases=this,canvas,data,layers,layer,l,matching=[];if($canvases.length!==0){canvas=$canvases[0];data=_getCanvasData(canvas);// Retrieve layers array for this canvaslayers=data.layers;// If a callback function is givenif(isFunction(callback)){// Filter the layers array using the callbackfor(l=0;l<layers.length;l+=1){layer=layers[l];if(callback.call(canvas,layer)){// Add layer to array of matching layers if test passesmatching.push(layer);}}}else{// Otherwise, get all layersmatching=layers;}}returnmatching;};// Get a single jCanvas layer object$.fn.getLayer=functiongetLayer(layerId){var$canvases=this,canvas,data,layers,layer,l,idType;if($canvases.length!==0){canvas=$canvases[0];data=_getCanvasData(canvas);layers=data.layers;idType=typeOf(layerId);if(layerId&&layerId.layer){// Return the actual layer object if givenlayer=layerId;}elseif(idType==='number'){// Retrieve the layer using the given index// Allow for negative indicesif(layerId<0){layerId=layers.length+layerId;}// Get layer with the given indexlayer=layers[layerId];}elseif(idType==='regexp'){// Get layer with the name that matches the given regexfor(l=0;l<layers.length;l+=1){// Check if layer matches nameif(isString(layers[l].name)&&layers[l].name.match(layerId)){layer=layers[l];break;}}}else{// Get layer with the given namelayer=data.layer.names[layerId];}}returnlayer;};// Get all layers in the given group$.fn.getLayerGroup=functiongetLayerGroup(groupId){var$canvases=this,canvas,data,groups,groupName,group,idType=typeOf(groupId);if($canvases.length!==0){canvas=$canvases[0];if(idType==='array'){// Return layer group if givengroup=groupId;}elseif(idType==='regexp'){// Get canvas datadata=_getCanvasData(canvas);groups=data.layer.groups;// Loop through all layers groups for this canvasfor(groupNameingroups){// Find a group whose name matches the given regexif(groupName.match(groupId)){group=groups[groupName];// Stop after finding the first matching groupbreak;}}}else{// Find layer group with the given group namedata=_getCanvasData(canvas);group=data.layer.groups[groupId];}}returngroup;};// Get index of layer in layers array$.fn.getLayerIndex=functiongetLayerIndex(layerId){var$canvases=this,layers=$canvases.getLayers(),layer=$canvases.getLayer(layerId);returninArray(layer,layers);};// Set properties of a layer$.fn.setLayer=functionsetLayer(layerId,props){var$canvases=this,$canvas,e,data,layer,propName,propValue,propType;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);layer=$($canvases[e]).getLayer(layerId);if(layer){// Update layer property maps_updateLayerName($canvas,data,layer,props);_updateLayerGroups($canvas,data,layer,props);_coerceNumericProps(props);// Merge properties with layerfor(propNameinprops){if(Object.prototype.hasOwnProperty.call(props,propName)){propValue=props[propName];propType=typeOf(propValue);if(propType==='object'&&isPlainObject(propValue)){// Clone objectslayer[propName]=extendObject({},propValue);_coerceNumericProps(layer[propName]);}elseif(propType==='array'){// Clone arrayslayer[propName]=propValue.slice(0);}elseif(propType==='string'){if(propValue.indexOf('+=')===0){// Increment numbers prefixed with +=layer[propName]+=parseFloat(propValue.substr(2));}elseif(propValue.indexOf('-=')===0){// Decrement numbers prefixed with -=layer[propName]-=parseFloat(propValue.substr(2));}elseif(!isNaN(propValue)&&isNumeric(propValue)&&propName!=='text'){// Convert numeric values as strings to numberslayer[propName]=parseFloat(propValue);}else{// Otherwise, set given string valuelayer[propName]=propValue;}}else{// Otherwise, set given valuelayer[propName]=propValue;}}}// Update layer events_addLayerEvents($canvas,data,layer);_enableDrag($canvas,data,layer);// If layer's properties were changedif($.isEmptyObject(props)===false){_triggerLayerEvent($canvas,data,layer,'change',props);}}}return$canvases;};// Set properties of all layers (optionally filtered by a callback)$.fn.setLayers=functionsetLayers(props,callback){var$canvases=this,$canvas,e,layers,l;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);layers=$canvas.getLayers(callback);// Loop through all layersfor(l=0;l<layers.length;l+=1){// Set properties of each layer$canvas.setLayer(layers[l],props);}}return$canvases;};// Set properties of all layers in the given group$.fn.setLayerGroup=functionsetLayerGroup(groupId,props){var$canvases=this,$canvas,e,group,l;for(e=0;e<$canvases.length;e+=1){// Get layer group$canvas=$($canvases[e]);group=$canvas.getLayerGroup(groupId);// If group existsif(group){// Loop through layers in groupfor(l=0;l<group.length;l+=1){// Merge given properties with layer$canvas.setLayer(group[l],props);}}}return$canvases;};// Move a layer to the given index in the layers array$.fn.moveLayer=functionmoveLayer(layerId,index){var$canvases=this,$canvas,e,data,layers,layer;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);// Retrieve layers array and desired layerlayers=data.layers;layer=$canvas.getLayer(layerId);if(layer){// Ensure layer index is accuratelayer.index=inArray(layer,layers);// Remove layer from its current placementlayers.splice(layer.index,1);// Add layer in its new placementlayers.splice(index,0,layer);// Handle negative indicesif(index<0){index=layers.length+index;}// Update layer's stored indexlayer.index=index;_triggerLayerEvent($canvas,data,layer,'move');}}return$canvases;};// Remove a jCanvas layer$.fn.removeLayer=functionremoveLayer(layerId){var$canvases=this,$canvas,e,data,layers,layer;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);// Retrieve layers array and desired layerlayers=$canvas.getLayers();layer=$canvas.getLayer(layerId);// Remove layer if foundif(layer){// Ensure layer index is accuratelayer.index=inArray(layer,layers);// Remove layer and allow it to be re-added laterlayers.splice(layer.index,1);deletelayer._layer;// Update layer name map_updateLayerName($canvas,data,layer,{name:null});// Update layer group map_updateLayerGroups($canvas,data,layer,{groups:null});// Trigger 'remove' event_triggerLayerEvent($canvas,data,layer,'remove');}}return$canvases;};// Remove all layers$.fn.removeLayers=functionremoveLayers(callback){var$canvases=this,$canvas,e,data,layers,layer,l;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);layers=$canvas.getLayers(callback);// Remove all layers individuallyfor(l=0;l<layers.length;l+=1){layer=layers[l];$canvas.removeLayer(layer);// Ensure no layer is skipped overl-=1;}// Update layer mapsdata.layer.names={};data.layer.groups={};}return$canvases;};// Remove all layers in the group with the given ID$.fn.removeLayerGroup=functionremoveLayerGroup(groupId){var$canvases=this,$canvas,e,group,l;if(groupId!==undefined){for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);group=$canvas.getLayerGroup(groupId);// Remove layer group using given group nameif(group){// Clone groups arraygroup=group.slice(0);// Loop through layers in groupfor(l=0;l<group.length;l+=1){$canvas.removeLayer(group[l]);}}}}return$canvases;};// Add an existing layer to a layer group$.fn.addLayerToGroup=functionaddLayerToGroup(layerId,groupName){var$canvases=this,$canvas,e,layer,groups=[groupName];for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);layer=$canvas.getLayer(layerId);// If layer is not already in groupif(layer.groups){// Clone groups listgroups=layer.groups.slice(0);// If layer is not already in groupif(inArray(groupName,layer.groups)===-1){// Add layer to groupgroups.push(groupName);}}// Update layer group maps$canvas.setLayer(layer,{groups:groups});}return$canvases;};// Remove an existing layer from a layer group$.fn.removeLayerFromGroup=functionremoveLayerFromGroup(layerId,groupName){var$canvases=this,$canvas,e,layer,groups=[],index;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);layer=$canvas.getLayer(layerId);if(layer.groups){// Find index of layer in groupindex=inArray(groupName,layer.groups);// If layer is in groupif(index!==-1){// Clone groups listgroups=layer.groups.slice(0);// Remove layer from groupgroups.splice(index,1);// Update layer group maps$canvas.setLayer(layer,{groups:groups});}}}return$canvases;};// Get topmost layer that intersects with event coordinatesfunction_getIntersectingLayer(data){varlayer,i,mask,m;// Store the topmost layerlayer=null;// Get the topmost layer whose visible area intersects event coordinatesfor(i=data.intersecting.length-1;i>=0;i-=1){// Get current layerlayer=data.intersecting[i];// If layer has previous masksif(layer._masks){// Search previous masks to ensure// layer is visible at event coordinatesfor(m=layer._masks.length-1;m>=0;m-=1){mask=layer._masks[m];// If mask does not intersect event coordinatesif(!mask.intersects){// Indicate that the mask does not// intersect event coordinateslayer.intersects=false;// Stop searching previous masksbreak;}}// If event coordinates intersect all previous masks// and layer is not intangibleif(layer.intersects&&!layer.intangible){// Stop searching for topmost layerbreak;}}}// If resulting layer is intangibleif(layer&&layer.intangible){// Cursor does not intersect this layerlayer=null;}returnlayer;}// Draw individual layer (internal)function_drawLayer($canvas,ctx,layer,nextLayerIndex){if(layer&&layer.visible&&layer._method){if(nextLayerIndex){layer._next=nextLayerIndex;}else{layer._next=null;}// If layer is an object, call its respective methodif(layer._method){layer._method.call($canvas,layer);}}}// Handle dragging of the currently-dragged layerfunction_handleLayerDrag($canvas,data,eventType){varlayers,layer,l,drag,dragGroups,group,groupName,g,newX,newY;drag=data.drag;layer=drag.layer;dragGroups=(layer&&layer.dragGroups)||[];layers=data.layers;if(eventType==='mousemove'||eventType==='touchmove'){// Detect when user is currently dragging layerif(!drag.dragging){// Detect when user starts dragging layer// Signify that a layer on the canvas is being draggeddrag.dragging=true;layer.dragging=true;// Optionally bring layer to front when drag startsif(layer.bringToFront){// Remove layer from its original positionlayers.splice(layer.index,1);// Bring layer to front// push() returns the new array lengthlayer.index=layers.push(layer);}// Set drag properties for this layerlayer._startX=layer.x;layer._startY=layer.y;layer._endX=layer._eventX;layer._endY=layer._eventY;// Trigger dragstart event_triggerLayerEvent($canvas,data,layer,'dragstart');}if(drag.dragging){// Calculate position after dragnewX=layer._eventX-(layer._endX-layer._startX);newY=layer._eventY-(layer._endY-layer._startY);if(layer.updateDragX){newX=layer.updateDragX.call($canvas[0],layer,newX);}if(layer.updateDragY){newY=layer.updateDragY.call($canvas[0],layer,newY);}layer.dx=newX-layer.x;layer.dy=newY-layer.y;if(layer.restrictDragToAxis!=='y'){layer.x=newX;}if(layer.restrictDragToAxis!=='x'){layer.y=newY;}// Trigger drag event_triggerLayerEvent($canvas,data,layer,'drag');// Move groups with layer on dragfor(g=0;g<dragGroups.length;g+=1){groupName=dragGroups[g];group=data.layer.groups[groupName];if(layer.groups&&group){for(l=0;l<group.length;l+=1){if(group[l]!==layer){if(layer.restrictDragToAxis!=='y'&&group[l].restrictDragToAxis!=='y'){group[l].x+=layer.dx;}if(layer.restrictDragToAxis!=='x'&&group[l].restrictDragToAxis!=='x'){group[l].y+=layer.dy;}}}}}}}elseif(eventType==='mouseup'||eventType==='touchend'){// Detect when user stops dragging layerif(drag.dragging){layer.dragging=false;drag.dragging=false;data.redrawOnMousemove=data.originalRedrawOnMousemove;// Trigger dragstop event_triggerLayerEvent($canvas,data,layer,'dragstop');}// Cancel draggingdata.drag={};}}// List of CSS3 cursors that need to be prefixedcss.cursors=['grab','grabbing','zoom-in','zoom-out'];// Function to detect vendor prefix// Modified version of David Walsh's implementation// https://davidwalsh.name/vendor-prefixcss.prefix=(function(){varstyles=getComputedStyle(document.documentElement,''),pre=(arraySlice.call(styles).join('').match(/-(moz|webkit|ms)-/)||(styles.OLink===''&&['','o']))[1];return'-'+pre+'-';})();// Set cursor on canvasfunction_setCursor($canvas,layer,eventType){varcursor;if(layer.cursors){// Retrieve cursor from cursors object if it existscursor=layer.cursors[eventType];}// Prefix any CSS3 cursorif($.inArray(cursor,css.cursors)!==-1){cursor=css.prefix+cursor;}// If cursor is definedif(cursor){// Set canvas cursor$canvas.css({cursor:cursor});}}// Reset cursor on canvasfunction_resetCursor($canvas,data){$canvas.css({cursor:data.cursor});}// Run the given event callback with the given argumentsfunction_runEventCallback($canvas,layer,eventType,callbacks,arg){// Prevent callback from firing recursivelyif(callbacks[eventType]&&layer._running&&!layer._running[eventType]){// Signify the start of callback execution for this eventlayer._running[eventType]=true;// Run event callback with the given argumentscallbacks[eventType].call($canvas[0],layer,arg);// Signify the end of callback execution for this eventlayer._running[eventType]=false;}}// Determine if the given layer can "legally" fire the given eventfunction_layerCanFireEvent(layer,eventType){// If events are disable and if// layer is tangible or event is not tangiblereturn(!layer.disableEvents&&(!layer.intangible||$.inArray(eventType,tangibleEvents)===-1));}// Trigger the given event on the given layerfunction_triggerLayerEvent($canvas,data,layer,eventType,arg){// If layer can legally fire this event typeif(_layerCanFireEvent(layer,eventType)){// Do not set a custom cursor on layer mouseoutif(eventType!=='mouseout'){// Update cursor if one is defined for this event_setCursor($canvas,layer,eventType);}// Trigger the user-defined event callback_runEventCallback($canvas,layer,eventType,layer,arg);// Trigger the canvas-bound event hook_runEventCallback($canvas,layer,eventType,data.eventHooks,arg);// Trigger the global event hook_runEventCallback($canvas,layer,eventType,jCanvas.eventHooks,arg);}}// Manually trigger a layer event$.fn.triggerLayerEvent=function(layer,eventType){var$canvases=this,$canvas,e,data;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);layer=$canvas.getLayer(layer);if(layer){_triggerLayerEvent($canvas,data,layer,eventType);}}return$canvases;};// Draw layer with the given ID$.fn.drawLayer=functiondrawLayer(layerId){var$canvases=this,e,ctx,$canvas,layer;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);ctx=_getContext($canvases[e]);if(ctx){layer=$canvas.getLayer(layerId);_drawLayer($canvas,ctx,layer);}}return$canvases;};// Draw all layers (or, if given, only layers starting at an index)$.fn.drawLayers=functiondrawLayers(args){var$canvases=this,$canvas,e,ctx,// Internal parameters for redrawing the canvasparams=args||{},// Other variableslayers,layer,lastLayer,l,index,lastIndex,data,eventCache,eventType,isImageLayer;// The layer index from which to start redrawing the canvasindex=params.index;if(!index){index=0;}for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);// Clear canvas first unless otherwise directedif(params.clear!==false){$canvas.clearCanvas();}// Cache the layers arraylayers=data.layers;// Draw layers from first to last (bottom to top)for(l=index;l<layers.length;l+=1){layer=layers[l];// Ensure layer index is up-to-datelayer.index=l;// Prevent any one event from firing excessivelyif(params.resetFire){layer._fired=false;}// Draw layer_drawLayer($canvas,ctx,layer,l+1);// Store list of previous masks for each layerlayer._masks=data.transforms.masks.slice(0);// Allow image layers to load before drawing successive layersif(layer._method===$.fn.drawImage&&layer.visible){isImageLayer=true;break;}}// If layer is an image layerif(isImageLayer){// Stop and wait for drawImage() to resume drawLayers()break;}// Store the latestlastIndex=l;// Get first layer that intersects with event coordinateslayer=_getIntersectingLayer(data);eventCache=data.event;eventType=eventCache.type;// If jCanvas has detected a dragstartif(data.drag.layer){// Handle dragging of layer_handleLayerDrag($canvas,data,eventType);}// Manage mouseout eventlastLayer=data.lastIntersected;if(lastLayer!==null&&layer!==lastLayer&&lastLayer._hovered&&!lastLayer._fired&&!data.drag.dragging){data.lastIntersected=null;lastLayer._fired=true;lastLayer._hovered=false;_triggerLayerEvent($canvas,data,lastLayer,'mouseout');_resetCursor($canvas,data);}if(layer){// Use mouse event callbacks if no touch event callbacks are givenif(!layer[eventType]){eventType=_getMouseEventName(eventType);}// Check events for intersecting layerif(layer._event&&layer.intersects){data.lastIntersected=layer;// Detect mouseover eventsif((layer.mouseover||layer.mouseout||layer.cursors)&&!data.drag.dragging){if(!layer._hovered&&!layer._fired){// Prevent events from firing excessivelylayer._fired=true;layer._hovered=true;_triggerLayerEvent($canvas,data,layer,'mouseover');}}// Detect any other mouse eventif(!layer._fired){// Prevent event from firing twice unintentionallylayer._fired=true;eventCache.type=null;_triggerLayerEvent($canvas,data,layer,eventType);}// Use the mousedown event to start dragif(layer.draggable&&!layer.disableEvents&&(eventType==='mousedown'||eventType==='touchstart')){// Keep track of drag statedata.drag.layer=layer;data.originalRedrawOnMousemove=data.redrawOnMousemove;data.redrawOnMousemove=true;}}}// If cursor is not intersecting with any layerif(layer===null&&!data.drag.dragging){// Reset cursor to previous state_resetCursor($canvas,data);}// If the last layer has been drawnif(lastIndex===layers.length){// Reset list of intersecting layersdata.intersecting.length=0;// Reset transformation stackdata.transforms=_cloneTransforms(baseTransforms);data.savedTransforms.length=0;}}}return$canvases;};// Add a jCanvas layer (internal)function_addLayer(canvas,params,args,method){var$canvas,data,layers,layer=(params._layer?args:params);// Store arguments object for later useparams._args=args;// Convert all draggable drawings into jCanvas layersif(params.draggable||params.dragGroups){params.layer=true;params.draggable=true;}// Determine the layer's type using the available informationif(!params._method){if(method){params._method=method;}elseif(params.method){params._method=$.fn[params.method];}elseif(params.type){params._method=$.fn[maps.drawings[params.type]];}}// If layer hasn't been added yetif(params.layer&&!params._layer){// Add layer to canvas$canvas=$(canvas);data=_getCanvasData(canvas);layers=data.layers;// Do not add duplicate layers of same nameif(layer.name===null||(isString(layer.name)&&data.layer.names[layer.name]===undefined)){// Convert number properties to numbers_coerceNumericProps(params);// Ensure layers are unique across canvases by cloning themlayer=newjCanvasObject(params);layer.canvas=canvas;// Indicate that this is a layer for future checkslayer.layer=true;layer._layer=true;layer._running={};// If layer stores user-defined dataif(layer.data!==null){// Clone objectlayer.data=extendObject({},layer.data);}else{// Otherwise, create data objectlayer.data={};}// If layer stores a list of associated groupsif(layer.groups!==null){// Clone listlayer.groups=layer.groups.slice(0);}else{// Otherwise, create empty listlayer.groups=[];}// Update layer group maps_updateLayerName($canvas,data,layer);_updateLayerGroups($canvas,data,layer);// Check for any associated jCanvas events and enable them_addLayerEvents($canvas,data,layer);// Optionally enable drag-and-drop support and cursor support_enableDrag($canvas,data,layer);// Copy _event property to parameters objectparams._event=layer._event;// Calculate width/height for text layersif(layer._method===$.fn.drawText){$canvas.measureText(layer);}// Add layer to end of array if no index is specifiedif(layer.index===null){layer.index=layers.length;}// Add layer to layers array at specified indexlayers.splice(layer.index,0,layer);// Store layer on parameters objectparams._args=layer;// Trigger an 'add' event_triggerLayerEvent($canvas,data,layer,'add');}}elseif(!params.layer){_coerceNumericProps(params);}returnlayer;}// Add a jCanvas layer$.fn.addLayer=functionaddLayer(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);params.layer=true;_addLayer($canvases[e],params,args);}}return$canvases;};/* Animation API */// Define properties used in both CSS and jCanvascss.props=['width','height','opacity','lineHeight'];css.propsObj={};// Hide/show jCanvas/CSS properties so they can be animated using jQueryfunction_showProps(obj){varcssProp,p;for(p=0;p<css.props.length;p+=1){cssProp=css.props[p];obj[cssProp]=obj['_'+cssProp];}}function_hideProps(obj,reset){varcssProp,p;for(p=0;p<css.props.length;p+=1){cssProp=css.props[p];// Hide property using same name with leading underscoreif(obj[cssProp]!==undefined){obj['_'+cssProp]=obj[cssProp];css.propsObj[cssProp]=true;if(reset){deleteobj[cssProp];}}}}// Evaluate property values that are functionsfunction_parseEndValues(canvas,layer,endValues){varpropName,propValue,subPropName,subPropValue;// Loop through all properties in map of end valuesfor(propNameinendValues){if(Object.prototype.hasOwnProperty.call(endValues,propName)){propValue=endValues[propName];// If end value is functionif(isFunction(propValue)){// Call function and use its value as the end valueendValues[propName]=propValue.call(canvas,layer,propName);}// If end value is an objectif(typeOf(propValue)==='object'&&isPlainObject(propValue)){// Prepare to animate properties in objectfor(subPropNameinpropValue){if(Object.prototype.hasOwnProperty.call(propValue,subPropName)){subPropValue=propValue[subPropName];// Store property's start value at top-level of layerif(layer[propName]!==undefined){layer[propName+'.'+subPropName]=layer[propName][subPropName];// Store property's end value at top-level of end values mapendValues[propName+'.'+subPropName]=subPropValue;}}}// Delete sub-property of object as it's no longer neededdeleteendValues[propName];}}}returnendValues;}// Remove sub-property aliases from layer objectfunction_removeSubPropAliases(layer){varpropName;for(propNameinlayer){if(Object.prototype.hasOwnProperty.call(layer,propName)){if(propName.indexOf('.')!==-1){deletelayer[propName];}}}}// Convert a color value to an array of RGB valuesfunction_colorToRgbArray(color){varoriginalColor,elem,rgb=[],multiple=1;// Deal with complete transparencyif(color==='transparent'){color='rgba(0, 0, 0, 0)';}elseif(color.match(/^([a-z]+|#[0-9a-f]+)$/gi)){// Deal with hexadecimal colors and color nameselem=document.head;originalColor=elem.style.color;elem.style.color=color;color=$.css(elem,'color');elem.style.color=originalColor;}// Parse RGB stringif(color.match(/^rgb/gi)){rgb=color.match(/(\d+(\.\d+)?)/gi);// Deal with RGB percentagesif(color.match(/%/gi)){multiple=2.55;}rgb[0]*=multiple;rgb[1]*=multiple;rgb[2]*=multiple;// Ad alpha channel if givenif(rgb[3]!==undefined){rgb[3]=parseFloat(rgb[3]);}else{rgb[3]=1;}}returnrgb;}// Animate a hex or RGB colorfunction_animateColor(fx){varn=3,i;// Only parse start and end colors onceif(typeOf(fx.start)!=='array'){fx.start=_colorToRgbArray(fx.start);fx.end=_colorToRgbArray(fx.end);}fx.now=[];// If colors are RGBA, animate transparencyif(fx.start[3]!==1||fx.end[3]!==1){n=4;}// Calculate current frame for red, green, blue, and alphafor(i=0;i<n;i+=1){fx.now[i]=fx.start[i]+((fx.end[i]-fx.start[i])*fx.pos);// Only the red, green, and blue values must be integersif(i<3){fx.now[i]=round(fx.now[i]);}}if(fx.start[3]!==1||fx.end[3]!==1){// Only use RGBA if RGBA colors are givenfx.now='rgba('+fx.now.join(',')+')';}else{// Otherwise, animate as solid colorsfx.now.slice(0,3);fx.now='rgb('+fx.now.join(',')+')';}// Animate colors for both canvas layers and DOM elementsif(fx.elem.nodeName){fx.elem.style[fx.prop]=fx.now;}else{fx.elem[fx.prop]=fx.now;}}// Animate jCanvas layer$.fn.animateLayer=functionanimateLayer(){var$canvases=this,$canvas,e,ctx,args=arraySlice.call(arguments,0),data,layer,props;// Deal with all cases of argument placement/* 0. layer name/index1.properties2.duration/options3.easing4.completefunction5.stepfunction*/ if (typeOf(args[2]) === 'object') { //Acceptanoptionsobjectforanimationargs.splice(2,0,args[2].duration||null);args.splice(3,0,args[3].easing||null);args.splice(4,0,args[4].complete||null);args.splice(5,0,args[5].step||null);}else{if(args[2]===undefined){// If object is the last argumentargs.splice(2,0,null);args.splice(3,0,null);args.splice(4,0,null);}elseif(isFunction(args[2])){// If callback comes after objectargs.splice(2,0,null);args.splice(3,0,null);}if(args[3]===undefined){// If duration is the last argumentargs[3]=null;args.splice(4,0,null);}elseif(isFunction(args[3])){// If callback comes after durationargs.splice(3,0,null);}}// Run callback function when animation completesfunctioncomplete($canvas,data,layer){returnfunction(){_showProps(layer);_removeSubPropAliases(layer);// Prevent multiple redraw loopsif(!data.animating||data.animated===layer){// Redraw layers on last frame$canvas.drawLayers();}// Signify the end of an animation looplayer._animating=false;data.animating=false;data.animated=null;// If callback is definedif(args[4]){// Run callback at the end of the animationargs[4].call($canvas[0],layer);}_triggerLayerEvent($canvas,data,layer,'animateend');};}// Redraw layers on every frame of the animationfunctionstep($canvas,data,layer){returnfunction(now,fx){varparts,propName,subPropName,hidden=false;// If animated property has been hiddenif(fx.prop[0]==='_'){hidden=true;// Unhide property temporarilyfx.prop=fx.prop.replace('_','');layer[fx.prop]=layer['_'+fx.prop];}// If animating property of sub-objectif(fx.prop.indexOf('.')!==-1){parts=fx.prop.split('.');propName=parts[0];subPropName=parts[1];if(layer[propName]){layer[propName][subPropName]=fx.now;}}// Throttle animation to improve efficiencyif(layer._pos!==fx.pos){layer._pos=fx.pos;// Signify the start of an animation loopif(!layer._animating&&!data.animating){layer._animating=true;data.animating=true;data.animated=layer;}// Prevent multiple redraw loopsif(!data.animating||data.animated===layer){// Redraw layers for every frame$canvas.drawLayers();}}// If callback is definedif(args[5]){// Run callback for each step of animationargs[5].call($canvas[0],now,fx,layer);}_triggerLayerEvent($canvas,data,layer,'animate',fx);// If property should be hidden during animationif(hidden){// Hide property againfx.prop='_'+fx.prop;}};}for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);// If a layer object was passed, use it the layer to be animatedlayer=$canvas.getLayer(args[0]);// Ignore layers that are functionsif(layer&&layer._method!==$.fn.draw){// Do not modify original objectprops=extendObject({},args[1]);props=_parseEndValues($canvases[e],layer,props);// Bypass jQuery CSS Hooks for CSS properties (width, opacity, etc.)_hideProps(props,true);_hideProps(layer);// Fix for jQuery's vendor prefixing support, which affects how width/height/opacity are animatedlayer.style=css.propsObj;// Animate layer$(layer).animate(props,{duration:args[2],easing:($.easing[args[3]]?args[3]:null),// When animation completescomplete:complete($canvas,data,layer),// Redraw canvas for every animation framestep:step($canvas,data,layer)});_triggerLayerEvent($canvas,data,layer,'animatestart');}}}return$canvases;};// Animate all layers in a layer group$.fn.animateLayerGroup=functionanimateLayerGroup(groupId){var$canvases=this,$canvas,e,args=arraySlice.call(arguments,0),group,l;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);group=$canvas.getLayerGroup(groupId);if(group){// Animate all layers in the groupfor(l=0;l<group.length;l+=1){// Replace first argument with layerargs[0]=group[l];$canvas.animateLayer.apply($canvas,args);}}}return$canvases;};// Delay layer animation by a given number of milliseconds$.fn.delayLayer=functiondelayLayer(layerId,duration){var$canvases=this,$canvas,e,data,layer;duration=duration||0;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);layer=$canvas.getLayer(layerId);// If layer existsif(layer){// Delay animation$(layer).delay(duration);_triggerLayerEvent($canvas,data,layer,'delay');}}return$canvases;};// Delay animation all layers in a layer group$.fn.delayLayerGroup=functiondelayLayerGroup(groupId,duration){var$canvases=this,$canvas,e,group,layer,l;duration=duration||0;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);group=$canvas.getLayerGroup(groupId);// Delay all layers in the groupif(group){for(l=0;l<group.length;l+=1){// Delay each layer in the grouplayer=group[l];$canvas.delayLayer(layer,duration);}}}return$canvases;};// Stop layer animation$.fn.stopLayer=functionstopLayer(layerId,clearQueue){var$canvases=this,$canvas,e,data,layer;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);data=_getCanvasData($canvases[e]);layer=$canvas.getLayer(layerId);// If layer existsif(layer){// Stop animation$(layer).stop(clearQueue);_triggerLayerEvent($canvas,data,layer,'stop');}}return$canvases;};// Stop animation of all layers in a layer group$.fn.stopLayerGroup=functionstopLayerGroup(groupId,clearQueue){var$canvases=this,$canvas,e,group,layer,l;for(e=0;e<$canvases.length;e+=1){$canvas=$($canvases[e]);group=$canvas.getLayerGroup(groupId);// Stop all layers in the groupif(group){for(l=0;l<group.length;l+=1){// Stop each layer in the grouplayer=group[l];$canvas.stopLayer(layer,clearQueue);}}}return$canvases;};// Enable animation for color propertiesfunction_supportColorProps(props){varp;for(p=0;p<props.length;p+=1){$.fx.step[props[p]]=_animateColor;}}// Enable animation for color properties_supportColorProps(['color','backgroundColor','borderColor','borderTopColor','borderRightColor','borderBottomColor','borderLeftColor','fillStyle','outlineColor','strokeStyle','shadowColor']);/* Event API */// Map standard mouse events to touch eventsmaps.touchEvents={'mousedown':'touchstart','mouseup':'touchend','mousemove':'touchmove'};// Map standard touch events to mouse eventsmaps.mouseEvents={'touchstart':'mousedown','touchend':'mouseup','touchmove':'mousemove'};// Convert mouse event name to a corresponding touch event name (if possible)function_getTouchEventName(eventName){// Detect touch event supportif(maps.touchEvents[eventName]){eventName=maps.touchEvents[eventName];}returneventName;}// Convert touch event name to a corresponding mouse event namefunction_getMouseEventName(eventName){if(maps.mouseEvents[eventName]){eventName=maps.mouseEvents[eventName];}returneventName;}// Bind event to jCanvas layer using standard jQuery eventsfunction_createEvent(eventName){jCanvas.events[eventName]=function($canvas,data){varhelperEventName,touchEventName,eventCache;// Retrieve canvas's event cacheeventCache=data.event;// Both mouseover/mouseout events will be managed by a single mousemove eventhelperEventName=(eventName==='mouseover'||eventName==='mouseout')?'mousemove':eventName;touchEventName=_getTouchEventName(helperEventName);functioneventCallback(event){// Cache current mouse position and redraw layerseventCache.x=event.offsetX;eventCache.y=event.offsetY;eventCache.type=helperEventName;eventCache.event=event;// Redraw layers on every trigger of the event; don't redraw if at// least one layer is draggable and there are no layers with// explicit mouseover/mouseout/mousemove eventsif(event.type!=='mousemove'||data.redrawOnMousemove||data.drag.dragging){$canvas.drawLayers({resetFire:true});}// Prevent default event behaviorevent.preventDefault();}// Ensure the event is not bound more than onceif(!data.events[helperEventName]){// Bind one canvas event which handles all layer events of that typeif(touchEventName!==helperEventName){$canvas.bind(helperEventName+'.jCanvas '+touchEventName+'.jCanvas',eventCallback);}else{$canvas.bind(helperEventName+'.jCanvas',eventCallback);}// Prevent this event from being bound twicedata.events[helperEventName]=true;}};}function_createEvents(eventNames){varn;for(n=0;n<eventNames.length;n+=1){_createEvent(eventNames[n]);}}// Populate jCanvas events object with some standard events_createEvents(['click','dblclick','mousedown','mouseup','mousemove','mouseover','mouseout','touchstart','touchmove','touchend','pointerdown','pointermove','pointerup','contextmenu']);// Check if event fires when a drawing is drawnfunction_detectEvents(canvas,ctx,params){varlayer,data,eventCache,intersects,transforms,x,y,angle;// Use the layer object stored by the given parameters objectlayer=params._args;// Canvas must have event bindingsif(layer){data=_getCanvasData(canvas);eventCache=data.event;if(eventCache.x!==null&&eventCache.y!==null){// Respect user-defined pixel ratiox=eventCache.x*data.pixelRatio;y=eventCache.y*data.pixelRatio;// Determine if the given coordinates are in the current pathintersects=ctx.isPointInPath(x,y)||(ctx.isPointInStroke&&ctx.isPointInStroke(x,y));}transforms=data.transforms;// Allow callback functions to retrieve the mouse coordinateslayer.eventX=eventCache.x;layer.eventY=eventCache.y;layer.event=eventCache.event;// Adjust coordinates to match current canvas transformation// Keep track of some transformation valuesangle=data.transforms.rotate;x=layer.eventX;y=layer.eventY;if(angle!==0){// Rotate coordinates if coordinate space has been rotatedlayer._eventX=(x*cos(-angle))-(y*sin(-angle));layer._eventY=(y*cos(-angle))+(x*sin(-angle));}else{// Otherwise, no calculations need to be madelayer._eventX=x;layer._eventY=y;}// Scale coordinateslayer._eventX/=transforms.scaleX;layer._eventY/=transforms.scaleY;// If layer intersects with cursorif(intersects){// Add it to a list of layers that intersect with cursordata.intersecting.push(layer);}layer.intersects=Boolean(intersects);}}// Normalize offsetX and offsetY for all browsers$.event.fix=function(event){varoffset,originalEvent,touches;event=jQueryEventFix.call($.event,event);originalEvent=event.originalEvent;// originalEvent does not exist for manually-triggered eventsif(originalEvent){touches=originalEvent.changedTouches;// If offsetX and offsetY are not supported, define themif(event.pageX!==undefined&&event.offsetX===undefined){try{offset=$(event.currentTarget).offset();if(offset){event.offsetX=event.pageX-offset.left;event.offsetY=event.pageY-offset.top;}}catch(error){// Fail silently}}elseif(touches){try{// Enable offsetX and offsetY for mobile devicesoffset=$(event.currentTarget).offset();if(offset){event.offsetX=touches[0].pageX-offset.left;event.offsetY=touches[0].pageY-offset.top;}}catch(error){// Fail silently}}}returnevent;};/* Drawing API */// Map drawing names with their respective method namesmaps.drawings={'arc':'drawArc','bezier':'drawBezier','ellipse':'drawEllipse','function':'draw','image':'drawImage','line':'drawLine','path':'drawPath','polygon':'drawPolygon','slice':'drawSlice','quadratic':'drawQuadratic','rectangle':'drawRect','text':'drawText','vector':'drawVector','save':'saveCanvas','restore':'restoreCanvas','rotate':'rotateCanvas','scale':'scaleCanvas','translate':'translateCanvas'};// Draws on canvas using a function$.fn.draw=functiondraw(args){var$canvases=this,e,ctx,params=newjCanvasObject(args);// Draw using any other methodif(maps.drawings[params.type]&¶ms.type!=='function'){$canvases[maps.drawings[params.type]](args);}else{for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,draw);if(params.visible){if(params.fn){// Call the given user-defined functionparams.fn.call($canvases[e],ctx,params);}}}}}return$canvases;};// Clears canvas$.fn.clearCanvas=functionclearCanvas(args){var$canvases=this,e,ctx,params=newjCanvasObject(args);for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){if(params.width===null||params.height===null){// Clear entire canvas if width/height is not given// Reset current transformation temporarily to ensure that the entire canvas is clearedctx.save();ctx.setTransform(1,0,0,1,0,0);ctx.clearRect(0,0,$canvases[e].width,$canvases[e].height);ctx.restore();}else{// Otherwise, clear the defined section of the canvas// Transform clear rectangle_addLayer($canvases[e],params,args,clearCanvas);_transformShape($canvases[e],ctx,params,params.width,params.height);ctx.clearRect(params.x-(params.width/2),params.y-(params.height/2),params.width,params.height);// Restore previous transformation_restoreTransform(ctx,params);}}}return$canvases;};/* Transformation API */// Restores canvas$.fn.saveCanvas=functionsaveCanvas(args){var$canvases=this,e,ctx,params,data,i;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);_addLayer($canvases[e],params,args,saveCanvas);// Restore a number of times using the given countfor(i=0;i<params.count;i+=1){_saveCanvas(ctx,data);}}}return$canvases;};// Restores canvas$.fn.restoreCanvas=functionrestoreCanvas(args){var$canvases=this,e,ctx,params,data,i;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);_addLayer($canvases[e],params,args,restoreCanvas);// Restore a number of times using the given countfor(i=0;i<params.count;i+=1){_restoreCanvas(ctx,data);}}}return$canvases;};// Rotates canvas (internal)function_rotateCanvas(ctx,params,transforms){// Get conversion factor for radiansparams._toRad=(params.inDegrees?(PI/180):1);// Rotate canvas using shape as center of rotationctx.translate(params.x,params.y);ctx.rotate(params.rotate*params._toRad);ctx.translate(-params.x,-params.y);// If transformation data was givenif(transforms){// Update transformation datatransforms.rotate+=(params.rotate*params._toRad);}}// Scales canvas (internal)function_scaleCanvas(ctx,params,transforms){// Scale both the x- and y- axis using the 'scale' propertyif(params.scale!==1){params.scaleX=params.scaleY=params.scale;}// Scale canvas using shape as center of rotationctx.translate(params.x,params.y);ctx.scale(params.scaleX,params.scaleY);ctx.translate(-params.x,-params.y);// If transformation data was givenif(transforms){// Update transformation datatransforms.scaleX*=params.scaleX;transforms.scaleY*=params.scaleY;}}// Translates canvas (internal)function_translateCanvas(ctx,params,transforms){// Translate both the x- and y-axis using the 'translate' propertyif(params.translate){params.translateX=params.translateY=params.translate;}// Translate canvasctx.translate(params.translateX,params.translateY);// If transformation data was givenif(transforms){// Update transformation datatransforms.translateX+=params.translateX;transforms.translateY+=params.translateY;}}// Rotates canvas$.fn.rotateCanvas=functionrotateCanvas(args){var$canvases=this,e,ctx,params,data;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);_addLayer($canvases[e],params,args,rotateCanvas);// Autosave transformation state by defaultif(params.autosave){// Automatically save transformation state by default_saveCanvas(ctx,data);}_rotateCanvas(ctx,params,data.transforms);}}return$canvases;};// Scales canvas$.fn.scaleCanvas=functionscaleCanvas(args){var$canvases=this,e,ctx,params,data;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);_addLayer($canvases[e],params,args,scaleCanvas);// Autosave transformation state by defaultif(params.autosave){// Automatically save transformation state by default_saveCanvas(ctx,data);}_scaleCanvas(ctx,params,data.transforms);}}return$canvases;};// Translates canvas$.fn.translateCanvas=functiontranslateCanvas(args){var$canvases=this,e,ctx,params,data;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);_addLayer($canvases[e],params,args,translateCanvas);// Autosave transformation state by defaultif(params.autosave){// Automatically save transformation state by default_saveCanvas(ctx,data);}_translateCanvas(ctx,params,data.transforms);}}return$canvases;};/* Shape API */// Draws rectangle$.fn.drawRect=functiondrawRect(args){var$canvases=this,e,ctx,params,x1,y1,x2,y2,r,temp;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawRect);if(params.visible){_transformShape($canvases[e],ctx,params,params.width,params.height);_setGlobalProps($canvases[e],ctx,params);ctx.beginPath();if(params.width&¶ms.height){x1=params.x-(params.width/2);y1=params.y-(params.height/2);r=abs(params.cornerRadius);// If corner radius is defined and is not zeroif(r){// Draw rectangle with rounded corners if cornerRadius is definedx2=params.x+(params.width/2);y2=params.y+(params.height/2);// Handle negative widthif(params.width<0){temp=x1;x1=x2;x2=temp;}// Handle negative heightif(params.height<0){temp=y1;y1=y2;y2=temp;}// Prevent over-rounded cornersif((x2-x1)-(2*r)<0){r=(x2-x1)/2;}if((y2-y1)-(2*r)<0){r=(y2-y1)/2;}// Draw rectanglectx.moveTo(x1+r,y1);ctx.lineTo(x2-r,y1);ctx.arc(x2-r,y1+r,r,3*PI/2,PI*2,false);ctx.lineTo(x2,y2-r);ctx.arc(x2-r,y2-r,r,0,PI/2,false);ctx.lineTo(x1+r,y2);ctx.arc(x1+r,y2-r,r,PI/2,PI,false);ctx.lineTo(x1,y1+r);ctx.arc(x1+r,y1+r,r,PI,3*PI/2,false);// Always close pathparams.closed=true;}else{// Otherwise, draw rectangle with square cornersctx.rect(x1,y1,params.width,params.height);}}// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Close rectangle path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Retrieves a coterminal angle between 0 and 2pi for the given anglefunction_getCoterminal(angle){while(angle<0){angle+=(2*PI);}returnangle;}// Retrieves the x-coordinate for the given angle in a circlefunction_getArcX(params,angle){returnparams.x+(params.radius*cos(angle));}// Retrieves the y-coordinate for the given angle in a circlefunction_getArcY(params,angle){returnparams.y+(params.radius*sin(angle));}// Draws arc (internal)function_drawArc(canvas,ctx,params,path){varx1,y1,x2,y2,x3,y3,x4,y4,offsetX,offsetY,diff;// Determine offset from draggingif(params===path){offsetX=0;offsetY=0;}else{offsetX=params.x;offsetY=params.y;}// Convert default end angle to radiansif(!path.inDegrees&&path.end===360){path.end=PI*2;}// Convert angles to radianspath.start*=params._toRad;path.end*=params._toRad;// Consider 0deg due north of arcpath.start-=(PI/2);path.end-=(PI/2);// Ensure arrows are pointed correctly for CCW arcsdiff=PI/180;if(path.ccw){diff*=-1;}// Calculate coordinates for start arrowx1=_getArcX(path,path.start+diff);y1=_getArcY(path,path.start+diff);x2=_getArcX(path,path.start);y2=_getArcY(path,path.start);_addStartArrow(canvas,ctx,params,path,x1,y1,x2,y2);// Draw arcctx.arc(path.x+offsetX,path.y+offsetY,path.radius,path.start,path.end,path.ccw);// Calculate coordinates for end arrowx3=_getArcX(path,path.end+diff);y3=_getArcY(path,path.end+diff);x4=_getArcX(path,path.end);y4=_getArcY(path,path.end);_addEndArrow(canvas,ctx,params,path,x4,y4,x3,y3);}// Draws arc or circle$.fn.drawArc=functiondrawArc(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawArc);if(params.visible){_transformShape($canvases[e],ctx,params,params.radius*2);_setGlobalProps($canvases[e],ctx,params);ctx.beginPath();_drawArc($canvases[e],ctx,params,params);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws ellipse$.fn.drawEllipse=functiondrawEllipse(args){var$canvases=this,e,ctx,params,controlW,controlH;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawEllipse);if(params.visible){_transformShape($canvases[e],ctx,params,params.width,params.height);_setGlobalProps($canvases[e],ctx,params);// Calculate control width and heightcontrolW=params.width*(4/3);controlH=params.height;// Create ellipse using curvesctx.beginPath();ctx.moveTo(params.x,params.y-(controlH/2));// Left sidectx.bezierCurveTo(params.x-(controlW/2),params.y-(controlH/2),params.x-(controlW/2),params.y+(controlH/2),params.x,params.y+(controlH/2));// Right sidectx.bezierCurveTo(params.x+(controlW/2),params.y+(controlH/2),params.x+(controlW/2),params.y-(controlH/2),params.x,params.y-(controlH/2));// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Always close pathparams.closed=true;_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws a regular (equal-angled) polygon$.fn.drawPolygon=functiondrawPolygon(args){var$canvases=this,e,ctx,params,theta,dtheta,hdtheta,apothem,x,y,i;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawPolygon);if(params.visible){_transformShape($canvases[e],ctx,params,params.radius*2);_setGlobalProps($canvases[e],ctx,params);// Polygon's central angledtheta=(2*PI)/params.sides;// Half of dthetahdtheta=dtheta/2;// Polygon's starting angletheta=hdtheta+(PI/2);// Distance from polygon's center to the middle of its sideapothem=params.radius*cos(hdtheta);// Calculate path and drawctx.beginPath();for(i=0;i<params.sides;i+=1){// Draw side of polygonx=params.x+(params.radius*cos(theta));y=params.y+(params.radius*sin(theta));// Plot point on polygonctx.lineTo(x,y);// Project side if chosenif(params.concavity){// Sides are projected from the polygon's apothemx=params.x+((apothem+(-apothem*params.concavity))*cos(theta+hdtheta));y=params.y+((apothem+(-apothem*params.concavity))*sin(theta+hdtheta));ctx.lineTo(x,y);}// Increment theta by delta thetatheta+=dtheta;}// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Always close pathparams.closed=true;_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws pie-shaped slice$.fn.drawSlice=functiondrawSlice(args){var$canvases=this,e,ctx,params,angle,dx,dy;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawSlice);if(params.visible){_transformShape($canvases[e],ctx,params,params.radius*2);_setGlobalProps($canvases[e],ctx,params);// Perform extra calculations// Convert angles to radiansparams.start*=params._toRad;params.end*=params._toRad;// Consider 0deg at north of arcparams.start-=(PI/2);params.end-=(PI/2);// Find positive equivalents of anglesparams.start=_getCoterminal(params.start);params.end=_getCoterminal(params.end);// Ensure start angle is less than end angleif(params.end<params.start){params.end+=(2*PI);}// Calculate angular position of sliceangle=((params.start+params.end)/2);// Calculate ratios for slice's angledx=(params.radius*params.spread*cos(angle));dy=(params.radius*params.spread*sin(angle));// Adjust position of sliceparams.x+=dx;params.y+=dy;// Draw slicectx.beginPath();ctx.arc(params.x,params.y,params.radius,params.start,params.end,params.ccw);ctx.lineTo(params.x,params.y);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Always close pathparams.closed=true;_closePath($canvases[e],ctx,params);}}}return$canvases;};/* Path API */// Adds arrow to path using the given propertiesfunction_addArrow(canvas,ctx,params,path,x1,y1,x2,y2){varleftX,leftY,rightX,rightY,offsetX,offsetY,angle;// If arrow radius is given and path is not closedif(path.arrowRadius&&!params.closed){// Calculate angleangle=atan2((y2-y1),(x2-x1));// Adjust angle correctlyangle-=PI;// Calculate offset to place arrow at edge of pathoffsetX=(params.strokeWidth*cos(angle));offsetY=(params.strokeWidth*sin(angle));// Calculate coordinates for left half of arrowleftX=x2+(path.arrowRadius*cos(angle+(path.arrowAngle/2)));leftY=y2+(path.arrowRadius*sin(angle+(path.arrowAngle/2)));// Calculate coordinates for right half of arrowrightX=x2+(path.arrowRadius*cos(angle-(path.arrowAngle/2)));rightY=y2+(path.arrowRadius*sin(angle-(path.arrowAngle/2)));// Draw left half of arrowctx.moveTo(leftX-offsetX,leftY-offsetY);ctx.lineTo(x2-offsetX,y2-offsetY);// Draw right half of arrowctx.lineTo(rightX-offsetX,rightY-offsetY);// Visually connect arrow to pathctx.moveTo(x2-offsetX,y2-offsetY);ctx.lineTo(x2+offsetX,y2+offsetY);// Move back to end of pathctx.moveTo(x2,y2);}}// Optionally adds arrow to start of pathfunction_addStartArrow(canvas,ctx,params,path,x1,y1,x2,y2){if(!path._arrowAngleConverted){path.arrowAngle*=params._toRad;path._arrowAngleConverted=true;}if(path.startArrow){_addArrow(canvas,ctx,params,path,x1,y1,x2,y2);}}// Optionally adds arrow to end of pathfunction_addEndArrow(canvas,ctx,params,path,x1,y1,x2,y2){if(!path._arrowAngleConverted){path.arrowAngle*=params._toRad;path._arrowAngleConverted=true;}if(path.endArrow){_addArrow(canvas,ctx,params,path,x1,y1,x2,y2);}}// Draws line (internal)function_drawLine(canvas,ctx,params,path){varl,lx,ly;l=2;_addStartArrow(canvas,ctx,params,path,path.x2+params.x,path.y2+params.y,path.x1+params.x,path.y1+params.y);if(path.x1!==undefined&&path.y1!==undefined){ctx.moveTo(path.x1+params.x,path.y1+params.y);}while(true){// Calculate next coordinateslx=path['x'+l];ly=path['y'+l];// If coordinates are givenif(lx!==undefined&&ly!==undefined){// Draw next linectx.lineTo(lx+params.x,ly+params.y);l+=1;}else{// Otherwise, stop drawingbreak;}}l-=1;// Optionally add arrows to path_addEndArrow(canvas,ctx,params,path,path['x'+(l-1)]+params.x,path['y'+(l-1)]+params.y,path['x'+l]+params.x,path['y'+l]+params.y);}// Draws line$.fn.drawLine=functiondrawLine(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawLine);if(params.visible){_transformShape($canvases[e],ctx,params);_setGlobalProps($canvases[e],ctx,params);// Draw each pointctx.beginPath();_drawLine($canvases[e],ctx,params,params);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws quadratic curve (internal)function_drawQuadratic(canvas,ctx,params,path){varl,lx,ly,lcx,lcy;l=2;_addStartArrow(canvas,ctx,params,path,path.cx1+params.x,path.cy1+params.y,path.x1+params.x,path.y1+params.y);if(path.x1!==undefined&&path.y1!==undefined){ctx.moveTo(path.x1+params.x,path.y1+params.y);}while(true){// Calculate next coordinateslx=path['x'+l];ly=path['y'+l];lcx=path['cx'+(l-1)];lcy=path['cy'+(l-1)];// If coordinates are givenif(lx!==undefined&&ly!==undefined&&lcx!==undefined&&lcy!==undefined){// Draw next curvectx.quadraticCurveTo(lcx+params.x,lcy+params.y,lx+params.x,ly+params.y);l+=1;}else{// Otherwise, stop drawingbreak;}}l-=1;_addEndArrow(canvas,ctx,params,path,path['cx'+(l-1)]+params.x,path['cy'+(l-1)]+params.y,path['x'+l]+params.x,path['y'+l]+params.y);}// Draws quadratic curve$.fn.drawQuadratic=functiondrawQuadratic(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawQuadratic);if(params.visible){_transformShape($canvases[e],ctx,params);_setGlobalProps($canvases[e],ctx,params);// Draw each pointctx.beginPath();_drawQuadratic($canvases[e],ctx,params,params);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws Bezier curve (internal)function_drawBezier(canvas,ctx,params,path){varl,lc,lx,ly,lcx1,lcy1,lcx2,lcy2;l=2;lc=1;_addStartArrow(canvas,ctx,params,path,path.cx1+params.x,path.cy1+params.y,path.x1+params.x,path.y1+params.y);if(path.x1!==undefined&&path.y1!==undefined){ctx.moveTo(path.x1+params.x,path.y1+params.y);}while(true){// Calculate next coordinateslx=path['x'+l];ly=path['y'+l];lcx1=path['cx'+lc];lcy1=path['cy'+lc];lcx2=path['cx'+(lc+1)];lcy2=path['cy'+(lc+1)];// If next coordinates are givenif(lx!==undefined&&ly!==undefined&&lcx1!==undefined&&lcy1!==undefined&&lcx2!==undefined&&lcy2!==undefined){// Draw next curvectx.bezierCurveTo(lcx1+params.x,lcy1+params.y,lcx2+params.x,lcy2+params.y,lx+params.x,ly+params.y);l+=1;lc+=2;}else{// Otherwise, stop drawingbreak;}}l-=1;lc-=2;_addEndArrow(canvas,ctx,params,path,path['cx'+(lc+1)]+params.x,path['cy'+(lc+1)]+params.y,path['x'+l]+params.x,path['y'+l]+params.y);}// Draws Bezier curve$.fn.drawBezier=functiondrawBezier(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawBezier);if(params.visible){_transformShape($canvases[e],ctx,params);_setGlobalProps($canvases[e],ctx,params);// Draw each pointctx.beginPath();_drawBezier($canvases[e],ctx,params,params);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Retrieves the x-coordinate for the given vector angle and lengthfunction_getVectorX(params,angle,length){angle*=params._toRad;angle-=(PI/2);return(length*cos(angle));}// Retrieves the y-coordinate for the given vector angle and lengthfunction_getVectorY(params,angle,length){angle*=params._toRad;angle-=(PI/2);return(length*sin(angle));}// Draws vector (internal) #2function_drawVector(canvas,ctx,params,path){varl,angle,length,offsetX,offsetY,x,y,x3,y3,x4,y4;// Determine offset from draggingif(params===path){offsetX=0;offsetY=0;}else{offsetX=params.x;offsetY=params.y;}l=1;x=x3=x4=path.x+offsetX;y=y3=y4=path.y+offsetY;_addStartArrow(canvas,ctx,params,path,x+_getVectorX(params,path.a1,path.l1),y+_getVectorY(params,path.a1,path.l1),x,y);// The vector starts at the given (x, y) coordinatesif(path.x!==undefined&&path.y!==undefined){ctx.moveTo(x,y);}while(true){angle=path['a'+l];length=path['l'+l];if(angle!==undefined&&length!==undefined){// Convert the angle to radians with 0 degrees starting at north// Keep track of last two coordinatesx3=x4;y3=y4;// Compute (x, y) coordinates from angle and lengthx4+=_getVectorX(params,angle,length);y4+=_getVectorY(params,angle,length);ctx.lineTo(x4,y4);l+=1;}else{// Otherwise, stop drawingbreak;}}_addEndArrow(canvas,ctx,params,path,x3,y3,x4,y4);}// Draws vector$.fn.drawVector=functiondrawVector(args){var$canvases=this,e,ctx,params;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawVector);if(params.visible){_transformShape($canvases[e],ctx,params);_setGlobalProps($canvases[e],ctx,params);// Draw each pointctx.beginPath();_drawVector($canvases[e],ctx,params,params);// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};// Draws a path consisting of one or more subpaths$.fn.drawPath=functiondrawPath(args){var$canvases=this,e,ctx,params,l,lp;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawPath);if(params.visible){_transformShape($canvases[e],ctx,params);_setGlobalProps($canvases[e],ctx,params);ctx.beginPath();l=1;while(true){lp=params['p'+l];if(lp!==undefined){lp=newjCanvasObject(lp);if(lp.type==='line'){_drawLine($canvases[e],ctx,params,lp);}elseif(lp.type==='quadratic'){_drawQuadratic($canvases[e],ctx,params,lp);}elseif(lp.type==='bezier'){_drawBezier($canvases[e],ctx,params,lp);}elseif(lp.type==='vector'){_drawVector($canvases[e],ctx,params,lp);}elseif(lp.type==='arc'){_drawArc($canvases[e],ctx,params,lp);}l+=1;}else{break;}}// Check for jCanvas events_detectEvents($canvases[e],ctx,params);// Optionally close path_closePath($canvases[e],ctx,params);}}}return$canvases;};/* Text API */// Calculates font string and set it as the canvas fontfunction_setCanvasFont(canvas,ctx,params){// Otherwise, use the given font attributesif(!isNaN(Number(params.fontSize))){// Give font size units if it doesn't have anyparams.fontSize+='px';}// Set font using given font propertiesctx.font=params.fontStyle+' '+params.fontSize+' '+params.fontFamily;}// Measures canvas textfunction_measureText(canvas,ctx,params,lines){varoriginalSize,curWidth,l,propCache=caches.propCache;// Used cached width/height if possibleif(propCache.text===params.text&&propCache.fontStyle===params.fontStyle&&propCache.fontSize===params.fontSize&&propCache.fontFamily===params.fontFamily&&propCache.maxWidth===params.maxWidth&&propCache.lineHeight===params.lineHeight){params.width=propCache.width;params.height=propCache.height;}else{// Calculate text dimensions only once// Calculate width of first line (for comparison)params.width=ctx.measureText(lines[0]).width;// Get width of longest linefor(l=1;l<lines.length;l+=1){curWidth=ctx.measureText(lines[l]).width;// Ensure text's width is the width of its longest lineif(curWidth>params.width){params.width=curWidth;}}// Save original font sizeoriginalSize=canvas.style.fontSize;// Temporarily set canvas font size to retrieve size in pixelscanvas.style.fontSize=params.fontSize;// Save text width and height in parameters objectparams.height=parseFloat($.css(canvas,'fontSize'))*lines.length*params.lineHeight;// Reset font size to original sizecanvas.style.fontSize=originalSize;}}// Wraps a string of text within a defined widthfunction_wrapText(ctx,params){varallText=String(params.text),// Maximum line width (optional)maxWidth=params.maxWidth,// Lines created by manual line breaks (\n)manualLines=allText.split('\n'),// All lines created manually and by wrappingallLines=[],// Other variableslines,line,l,text,words,w;// Loop through manually-broken linesfor(l=0;l<manualLines.length;l+=1){text=manualLines[l];// Split line into list of wordswords=text.split(' ');lines=[];line='';// If text is short enough initially// Or, if the text consists of only one wordif(words.length===1||ctx.measureText(text).width<maxWidth){// No need to wrap textlines=[text];}else{// Wrap linesfor(w=0;w<words.length;w+=1){// Once line gets too wide, push word to next lineif(ctx.measureText(line+words[w]).width>maxWidth){// This check prevents empty lines from being createdif(line!==''){lines.push(line);}// Start new line and repeat processline='';}// Add words to line until the line is too wideline+=words[w];// Do not add a space after the last wordif(w!==(words.length-1)){line+=' ';}}// The last word should always be pushedlines.push(line);}// Remove extra space at the end of each lineallLines=allLines.concat(lines.join('\n').replace(/((\n))|($)/gi,'$2').split('\n'));}returnallLines;}// Draws text on canvas$.fn.drawText=functiondrawText(args){var$canvases=this,e,ctx,params,layer,lines,line,l,fontSize,constantCloseness=500,nchars,chars,ch,c,x,y;for(e=0;e<$canvases.length;e+=1){ctx=_getContext($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer($canvases[e],params,args,drawText);if(params.visible){// Set text-specific propertiesctx.textBaseline=params.baseline;ctx.textAlign=params.align;// Set canvas font using given properties_setCanvasFont($canvases[e],ctx,params);if(params.maxWidth!==null){// Wrap text using an internal functionlines=_wrapText(ctx,params);}else{// Convert string of text to list of lineslines=params.text.toString().split('\n');}// Calculate text's width and height_measureText($canvases[e],ctx,params,lines);// If text is a layerif(layer){// Copy calculated width/height to layer objectlayer.width=params.width;layer.height=params.height;}_transformShape($canvases[e],ctx,params,params.width,params.height);_setGlobalProps($canvases[e],ctx,params);// Adjust text position to accomodate different horizontal alignmentsx=params.x;if(params.align==='left'){if(params.respectAlign){// Realign text to the left if chosenparams.x+=params.width/2;}else{// Center text block by defaultx-=params.width/2;}}elseif(params.align==='right'){if(params.respectAlign){// Realign text to the right if chosenparams.x-=params.width/2;}else{// Center text block by defaultx+=params.width/2;}}if(params.radius){fontSize=parseFloat(params.fontSize);// Greater values move clockwiseif(params.letterSpacing===null){params.letterSpacing=fontSize/constantCloseness;}// Loop through each line of textfor(l=0;l<lines.length;l+=1){ctx.save();ctx.translate(params.x,params.y);line=lines[l];if(params.flipArcText){chars=line.split('');chars.reverse();line=chars.join('');}nchars=line.length;ctx.rotate(-(PI*params.letterSpacing*(nchars-1))/2);// Loop through characters on each linefor(c=0;c<nchars;c+=1){ch=line[c];// If character is not the first characterif(c!==0){// Rotate character onto arcctx.rotate(PI*params.letterSpacing);}ctx.save();ctx.translate(0,-params.radius);if(params.flipArcText){ctx.scale(-1,-1);}ctx.fillText(ch,0,0);// Prevent extra shadow created by stroke (but only when fill is present)if(params.fillStyle!=='transparent'){ctx.shadowColor='transparent';}if(params.strokeWidth!==0){// Only stroke if the stroke is not 0ctx.strokeText(ch,0,0);}ctx.restore();}params.radius-=fontSize;params.letterSpacing+=fontSize/(constantCloseness*2*PI);ctx.restore();}}else{// Draw each line of text separatelyfor(l=0;l<lines.length;l+=1){line=lines[l];// Add line offset to center point, but subtract some to center everythingy=params.y+(l*params.height/lines.length)-(((lines.length-1)*params.height/lines.length)/2);ctx.shadowColor=params.shadowColor;// Fill & stroke textctx.fillText(line,x,y);// Prevent extra shadow created by stroke (but only when fill is present)if(params.fillStyle!=='transparent'){ctx.shadowColor='transparent';}if(params.strokeWidth!==0){// Only stroke if the stroke is not 0ctx.strokeText(line,x,y);}}}// Adjust bounding box according to text baseliney=0;if(params.baseline==='top'){y+=params.height/2;}elseif(params.baseline==='bottom'){y-=params.height/2;}// Detect jCanvas eventsif(params._event){ctx.beginPath();ctx.rect(params.x-(params.width/2),params.y-(params.height/2)+y,params.width,params.height);_detectEvents($canvases[e],ctx,params);// Close path and configure maskingctx.closePath();}_restoreTransform(ctx,params);}}}// Cache jCanvas parameters object for efficiencycaches.propCache=params;return$canvases;};// Measures text width/height using the given parameters$.fn.measureText=functionmeasureText(args){var$canvases=this,ctx,params,lines;// Attempt to retrieve layerparams=$canvases.getLayer(args);// If layer does not exist or if returned object is not a jCanvas layerif(!params||(params&&!params._layer)){params=newjCanvasObject(args);}ctx=_getContext($canvases[0]);if(ctx){// Set canvas font using given properties_setCanvasFont($canvases[0],ctx,params);// Calculate width and height of textif(params.maxWidth!==null){lines=_wrapText(ctx,params);}else{lines=params.text.split('\n');}_measureText($canvases[0],ctx,params,lines);}returnparams;};/* Image API */// Draws image on canvas$.fn.drawImage=functiondrawImage(args){var$canvases=this,canvas,e,ctx,data,params,layer,img,imgCtx,source,imageCache=caches.imageCache;// Draw image functionfunctiondraw(canvas,ctx,data,params,layer){// If width and sWidth are not defined, use image widthif(params.width===null&¶ms.sWidth===null){params.width=params.sWidth=img.width;}// If width and sHeight are not defined, use image heightif(params.height===null&¶ms.sHeight===null){params.height=params.sHeight=img.height;}// Ensure image layer's width and height are accurateif(layer){layer.width=params.width;layer.height=params.height;}// Only crop image if all cropping properties are givenif(params.sWidth!==null&¶ms.sHeight!==null&¶ms.sx!==null&¶ms.sy!==null){// If width is not defined, use the given sWidthif(params.width===null){params.width=params.sWidth;}// If height is not defined, use the given sHeightif(params.height===null){params.height=params.sHeight;}// Optionally crop from top-left corner of regionif(params.cropFromCenter){params.sx+=params.sWidth/2;params.sy+=params.sHeight/2;}// Ensure cropped region does not escape image boundaries// Topif((params.sy-(params.sHeight/2))<0){params.sy=(params.sHeight/2);}// Bottomif((params.sy+(params.sHeight/2))>img.height){params.sy=img.height-(params.sHeight/2);}// Leftif((params.sx-(params.sWidth/2))<0){params.sx=(params.sWidth/2);}// Rightif((params.sx+(params.sWidth/2))>img.width){params.sx=img.width-(params.sWidth/2);}_transformShape(canvas,ctx,params,params.width,params.height);_setGlobalProps(canvas,ctx,params);// Draw imagectx.drawImage(img,params.sx-(params.sWidth/2),params.sy-(params.sHeight/2),params.sWidth,params.sHeight,params.x-(params.width/2),params.y-(params.height/2),params.width,params.height);}else{// Show entire image if no crop region is defined_transformShape(canvas,ctx,params,params.width,params.height);_setGlobalProps(canvas,ctx,params);// Draw image on canvasctx.drawImage(img,params.x-(params.width/2),params.y-(params.height/2),params.width,params.height);}// Draw invisible rectangle to allow for events and maskingctx.beginPath();ctx.rect(params.x-(params.width/2),params.y-(params.height/2),params.width,params.height);// Check for jCanvas events_detectEvents(canvas,ctx,params);// Close path and configure maskingctx.closePath();_restoreTransform(ctx,params);_enableMasking(ctx,data,params);}// On load functionfunctiononload(canvas,ctx,data,params,layer){returnfunction(){var$canvas=$(canvas);draw(canvas,ctx,data,params,layer);if(params.layer){// Trigger 'load' event for layers_triggerLayerEvent($canvas,data,layer,'load');}elseif(params.load){// Run 'load' callback for non-layersparams.load.call($canvas[0],layer);}// Continue drawing successive layers after this image layer has loadedif(params.layer){// Store list of previous masks for each layerlayer._masks=data.transforms.masks.slice(0);if(params._next){// Draw successive layers$canvas.drawLayers({clear:false,resetFire:true,index:params._next});}}};}for(e=0;e<$canvases.length;e+=1){canvas=$canvases[e];ctx=_getContext($canvases[e]);if(ctx){data=_getCanvasData($canvases[e]);params=newjCanvasObject(args);layer=_addLayer($canvases[e],params,args,drawImage);if(params.visible){// Cache the given sourcesource=params.source;imgCtx=source.getContext;if(source.src||imgCtx){// Use image or canvas element if givenimg=source;}elseif(source){if(imageCache[source]&&imageCache[source].complete){// Get the image element from the cache if possibleimg=imageCache[source];}else{// Otherwise, get the image from the given source URLimg=newImage();// If source URL is not a data URLif(!source.match(/^data:/i)){// Set crossOrigin for this imageimg.crossOrigin=params.crossOrigin;}img.src=source;// Save image in cache for improved performanceimageCache[source]=img;}}if(img){if(img.complete||imgCtx){// Draw image if already loadedonload(canvas,ctx,data,params,layer)();}else{// Otherwise, draw image when it loadsimg.onload=onload(canvas,ctx,data,params,layer);// Fix onload() bug in IE9img.src=img.src;}}}}}return$canvases;};// Creates a canvas pattern object$.fn.createPattern=functioncreatePattern(args){var$canvases=this,ctx,params,img,imgCtx,pattern,source;// Function to be called when pattern loadsfunctiononload(){// Create patternpattern=ctx.createPattern(img,params.repeat);// Run callback function if definedif(params.load){params.load.call($canvases[0],pattern);}}ctx=_getContext($canvases[0]);if(ctx){params=newjCanvasObject(args);// Cache the given sourcesource=params.source;// Draw when image is loaded (if load() callback function is defined)if(isFunction(source)){// Draw pattern using function if givenimg=$('<canvas />')[0];img.width=params.width;img.height=params.height;imgCtx=_getContext(img);source.call(img,imgCtx);onload();}else{// Otherwise, draw pattern using source imageimgCtx=source.getContext;if(source.src||imgCtx){// Use image element if givenimg=source;}else{// Use URL if given to get the imageimg=newImage();// If source URL is not a data URLif(!source.match(/^data:/i)){// Set crossOrigin for this imageimg.crossOrigin=params.crossOrigin;}img.src=source;}// Create pattern if already loadedif(img.complete||imgCtx){onload();}else{img.onload=onload;// Fix onload() bug in IE9img.src=img.src;}}}else{pattern=null;}returnpattern;};// Creates a canvas gradient object$.fn.createGradient=functioncreateGradient(args){var$canvases=this,ctx,params,gradient,stops=[],nstops,start,end,i,a,n,p;params=newjCanvasObject(args);ctx=_getContext($canvases[0]);if(ctx){// Gradient coordinates must be definedparams.x1=params.x1||0;params.y1=params.y1||0;params.x2=params.x2||0;params.y2=params.y2||0;if(params.r1!==null&¶ms.r2!==null){// Create radial gradient if chosengradient=ctx.createRadialGradient(params.x1,params.y1,params.r1,params.x2,params.y2,params.r2);}else{// Otherwise, create a linear gradient by defaultgradient=ctx.createLinearGradient(params.x1,params.y1,params.x2,params.y2);}// Count number of color stopsfor(i=1;params['c'+i]!==undefined;i+=1){if(params['s'+i]!==undefined){stops.push(params['s'+i]);}else{stops.push(null);}}nstops=stops.length;// Define start stop if not already definedif(stops[0]===null){stops[0]=0;}// Define end stop if not already definedif(stops[nstops-1]===null){stops[nstops-1]=1;}// Loop through color stops to fill in the blanksfor(i=0;i<nstops;i+=1){// A progression, in this context, is defined as all of the color stops between and including two known color stopsif(stops[i]!==null){// Start a new progression if stop is a number// Number of stops in current progressionn=1;// Current iteration in current progressionp=0;start=stops[i];// Look ahead to find end stopfor(a=(i+1);a<nstops;a+=1){if(stops[a]!==null){// If this future stop is a number, make it the end stop for this progressionend=stops[a];break;}else{// Otherwise, keep looking aheadn+=1;}}// Ensure start stop is not greater than end stopif(start>end){stops[a]=stops[i];}}elseif(stops[i]===null){// Calculate stop if not initially givenp+=1;stops[i]=start+(p*((end-start)/n));}// Add color stop to gradient objectgradient.addColorStop(stops[i],params['c'+(i+1)]);}}else{gradient=null;}returngradient;};// Manipulates pixels on the canvas$.fn.setPixels=functionsetPixels(args){var$canvases=this,canvas,e,ctx,canvasData,params,px,imgData,pixelData,i,len;for(e=0;e<$canvases.length;e+=1){canvas=$canvases[e];ctx=_getContext(canvas);canvasData=_getCanvasData($canvases[e]);if(ctx){params=newjCanvasObject(args);_addLayer(canvas,params,args,setPixels);_transformShape($canvases[e],ctx,params,params.width,params.height);// Use entire canvas of x, y, width, or height is not definedif(params.width===null||params.height===null){params.width=canvas.width;params.height=canvas.height;params.x=params.width/2;params.y=params.height/2;}if(params.width!==0&¶ms.height!==0){// Only set pixels if width and height are not zeroimgData=ctx.getImageData((params.x-(params.width/2))*canvasData.pixelRatio,(params.y-(params.height/2))*canvasData.pixelRatio,params.width*canvasData.pixelRatio,params.height*canvasData.pixelRatio);pixelData=imgData.data;len=pixelData.length;// Loop through pixels with the "each" callback functionif(params.each){for(i=0;i<len;i+=4){px={r:pixelData[i],g:pixelData[i+1],b:pixelData[i+2],a:pixelData[i+3]};params.each.call(canvas,px,params);pixelData[i]=px.r;pixelData[i+1]=px.g;pixelData[i+2]=px.b;pixelData[i+3]=px.a;}}// Put pixels on canvasctx.putImageData(imgData,(params.x-(params.width/2))*canvasData.pixelRatio,(params.y-(params.height/2))*canvasData.pixelRatio);// Restore transformationctx.restore();}}}return$canvases;};// Retrieves canvas image as data URL$.fn.getCanvasImage=functiongetCanvasImage(type,quality){var$canvases=this,canvas,dataURL=null;if($canvases.length!==0){canvas=$canvases[0];if(canvas.toDataURL){// JPEG quality defaults to 1if(quality===undefined){quality=1;}dataURL=canvas.toDataURL('image/'+type,quality);}}returndataURL;};// Scales canvas based on the device's pixel ratio$.fn.detectPixelRatio=functiondetectPixelRatio(callback){var$canvases=this,canvas,e,ctx,devicePixelRatio,backingStoreRatio,ratio,oldWidth,oldHeight,data;for(e=0;e<$canvases.length;e+=1){// Get canvas and its associated datacanvas=$canvases[e];ctx=_getContext(canvas);data=_getCanvasData($canvases[e]);// If canvas has not already been scaled with this methodif(!data.scaled){// Determine device pixel ratiosdevicePixelRatio=window.devicePixelRatio||1;backingStoreRatio=ctx.webkitBackingStorePixelRatio||ctx.mozBackingStorePixelRatio||ctx.msBackingStorePixelRatio||ctx.oBackingStorePixelRatio||ctx.backingStorePixelRatio||1;// Calculate general ratio based on the two given ratiosratio=devicePixelRatio/backingStoreRatio;if(ratio!==1){// Scale canvas relative to ratio// Get the current canvas dimensions for future useoldWidth=canvas.width;oldHeight=canvas.height;// Resize canvas relative to the determined ratiocanvas.width=oldWidth*ratio;canvas.height=oldHeight*ratio;// Scale canvas back to original dimensions via CSScanvas.style.width=oldWidth+'px';canvas.style.height=oldHeight+'px';// Scale context to counter the manual scaling of canvasctx.scale(ratio,ratio);}// Set pixel ratio on canvas data objectdata.pixelRatio=ratio;// Ensure that this method can only be called once for any given canvasdata.scaled=true;// Call the given callback function with the ratio as its only argumentif(callback){callback.call(canvas,ratio);}}}return$canvases;};// Clears the jCanvas cachejCanvas.clearCache=functionclearCache(){varcacheName;for(cacheNameincaches){if(Object.prototype.hasOwnProperty.call(caches,cacheName)){caches[cacheName]={};}}};// Enable canvas feature detection with $.support$.support.canvas=($('<canvas />')[0].getContext!==undefined);// Export jCanvas functionsextendObject(jCanvas,{defaults:defaults,setGlobalProps:_setGlobalProps,transformShape:_transformShape,detectEvents:_detectEvents,closePath:_closePath,setCanvasFont:_setCanvasFont,measureText:_measureText});$.jCanvas=jCanvas;$.jCanvasObject=jCanvasObject;}));