2010-07-20 07:59:47 -05:00
/ *
2010-02-05 21:36:35 -06:00
* svgcanvas . js
*
* Licensed under the Apache License , Version 2
*
* Copyright ( c ) 2010 Alexis Deveria
* Copyright ( c ) 2010 Pavol Rusnak
* Copyright ( c ) 2010 Jeff Schiller
*
* /
if ( ! window . console ) {
window . console = { } ;
window . console . log = function ( str ) { } ;
window . console . dir = function ( str ) { } ;
}
if ( window . opera ) {
window . console . log = function ( str ) { opera . postError ( str ) ; } ;
window . console . dir = function ( str ) { } ;
}
2010-02-08 21:39:35 -06:00
( function ( ) {
// This fixes $(...).attr() to work as expected with SVG elements.
// Does not currently use *AttributeNS() since we rarely need that.
// See http://api.jquery.com/attr/ for basic documentation of .attr()
// Additional functionality:
// - When getting attributes, a string that's a number is return as type number.
// - If an array is supplied as first parameter, multiple values are returned
// as an object with values for each given attributes
2010-09-10 15:19:23 -05:00
2010-02-08 21:39:35 -06:00
var proxied = jQuery . fn . attr , svgns = "http://www.w3.org/2000/svg" ;
jQuery . fn . attr = function ( key , value ) {
var len = this . length ;
if ( ! len ) return this ;
for ( var i = 0 ; i < len ; i ++ ) {
var elem = this [ i ] ;
// set/get SVG attribute
if ( elem . namespaceURI === svgns ) {
// Setting attribute
if ( value !== undefined ) {
elem . setAttribute ( key , value ) ;
} else if ( $ . isArray ( key ) ) {
// Getting attributes from array
var j = key . length , obj = { } ;
while ( j -- ) {
var aname = key [ j ] ;
var attr = elem . getAttribute ( aname ) ;
// This returns a number when appropriate
if ( attr || attr === "0" ) {
attr = isNaN ( attr ) ? attr : attr - 0 ;
}
obj [ aname ] = attr ;
}
return obj ;
} else if ( typeof key === "object" ) {
// Setting attributes form object
2010-03-15 11:13:22 -05:00
for ( var v in key ) {
2010-02-08 21:39:35 -06:00
elem . setAttribute ( v , key [ v ] ) ;
}
// Getting attribute
} else {
var attr = elem . getAttribute ( key ) ;
if ( attr || attr === "0" ) {
attr = isNaN ( attr ) ? attr : attr - 0 ;
}
return attr ;
}
} else {
return proxied . apply ( this , arguments ) ;
}
}
return this ;
} ;
} ( ) ) ;
2010-06-22 08:26:15 -05:00
// Class: SvgCanvas
// The main SvgCanvas class that manages all SVG-related functions
//
// Parameters:
// container - The container HTML element that should hold the SVG root element
// config - An object that contains configuration data
2010-03-12 03:50:23 -06:00
$ . SvgCanvas = function ( container , config )
2010-02-05 21:36:35 -06:00
{
var isOpera = ! ! window . opera ,
isWebkit = navigator . userAgent . indexOf ( "AppleWebKit" ) != - 1 ,
2010-06-22 08:26:15 -05:00
// Object populated later with booleans indicating support for features
2010-02-05 21:36:35 -06:00
support = { } ,
2010-06-22 08:26:15 -05:00
// this defines which elements and attributes that we support
2010-02-05 21:36:35 -06:00
svgWhiteList = {
2010-02-10 00:11:14 -06:00
// SVG Elements
2010-02-05 21:36:35 -06:00
"a" : [ "class" , "clip-path" , "clip-rule" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "mask" , "opacity" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" , "xlink:href" , "xlink:title" ] ,
"circle" : [ "class" , "clip-path" , "clip-rule" , "cx" , "cy" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "mask" , "opacity" , "r" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
"clipPath" : [ "class" , "clipPathUnits" , "id" ] ,
"defs" : [ ] ,
"desc" : [ ] ,
"ellipse" : [ "class" , "clip-path" , "clip-rule" , "cx" , "cy" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "mask" , "opacity" , "requiredFeatures" , "rx" , "ry" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
2010-04-01 23:56:21 -05:00
"feGaussianBlur" : [ "class" , "color-interpolation-filters" , "id" , "requiredFeatures" , "stdDeviation" ] ,
"filter" : [ "class" , "color-interpolation-filters" , "filterRes" , "filterUnits" , "height" , "id" , "primitiveUnits" , "requiredFeatures" , "width" , "x" , "xlink:href" , "y" ] ,
2010-03-06 18:28:51 -06:00
"foreignObject" : [ "class" , "font-size" , "height" , "id" , "opacity" , "requiredFeatures" , "style" , "transform" , "width" , "x" , "y" ] ,
2010-02-05 21:36:35 -06:00
"g" : [ "class" , "clip-path" , "clip-rule" , "id" , "display" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "mask" , "opacity" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
"image" : [ "class" , "clip-path" , "clip-rule" , "filter" , "height" , "id" , "mask" , "opacity" , "requiredFeatures" , "style" , "systemLanguage" , "transform" , "width" , "x" , "xlink:href" , "xlink:title" , "y" ] ,
"line" : [ "class" , "clip-path" , "clip-rule" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "opacity" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" , "x1" , "x2" , "y1" , "y2" ] ,
"linearGradient" : [ "class" , "id" , "gradientTransform" , "gradientUnits" , "requiredFeatures" , "spreadMethod" , "systemLanguage" , "x1" , "x2" , "xlink:href" , "y1" , "y2" ] ,
"marker" : [ "id" , "class" , "markerHeight" , "markerUnits" , "markerWidth" , "orient" , "preserveAspectRatio" , "refX" , "refY" , "systemLanguage" , "viewBox" ] ,
"mask" : [ "class" , "height" , "id" , "maskContentUnits" , "maskUnits" , "width" , "x" , "y" ] ,
"metadata" : [ "class" , "id" ] ,
"path" : [ "class" , "clip-path" , "clip-rule" , "d" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "opacity" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
"pattern" : [ "class" , "height" , "id" , "patternContentUnits" , "patternTransform" , "patternUnits" , "requiredFeatures" , "style" , "systemLanguage" , "width" , "x" , "xlink:href" , "y" ] ,
"polygon" : [ "class" , "clip-path" , "clip-rule" , "id" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "id" , "class" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "opacity" , "points" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
"polyline" : [ "class" , "clip-path" , "clip-rule" , "id" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "opacity" , "points" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" ] ,
"radialGradient" : [ "class" , "cx" , "cy" , "fx" , "fy" , "gradientTransform" , "gradientUnits" , "id" , "r" , "requiredFeatures" , "spreadMethod" , "systemLanguage" , "xlink:href" ] ,
"rect" : [ "class" , "clip-path" , "clip-rule" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "height" , "id" , "mask" , "opacity" , "requiredFeatures" , "rx" , "ry" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" , "width" , "x" , "y" ] ,
"stop" : [ "class" , "id" , "offset" , "requiredFeatures" , "stop-color" , "stop-opacity" , "style" , "systemLanguage" ] ,
2010-03-06 18:28:51 -06:00
"svg" : [ "class" , "clip-path" , "clip-rule" , "filter" , "id" , "height" , "mask" , "preserveAspectRatio" , "requiredFeatures" , "style" , "systemLanguage" , "viewBox" , "width" , "x" , "xmlns" , "xmlns:se" , "xmlns:xlink" , "y" ] ,
2010-02-05 21:36:35 -06:00
"switch" : [ "class" , "id" , "requiredFeatures" , "systemLanguage" ] ,
"symbol" : [ "class" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "font-family" , "font-size" , "font-style" , "font-weight" , "id" , "opacity" , "preserveAspectRatio" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "transform" , "viewBox" ] ,
"text" : [ "class" , "clip-path" , "clip-rule" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "font-family" , "font-size" , "font-style" , "font-weight" , "id" , "mask" , "opacity" , "requiredFeatures" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "text-anchor" , "transform" , "x" , "xml:space" , "y" ] ,
"textPath" : [ "class" , "id" , "method" , "requiredFeatures" , "spacing" , "startOffset" , "style" , "systemLanguage" , "transform" , "xlink:href" ] ,
"title" : [ ] ,
"tspan" : [ "class" , "clip-path" , "clip-rule" , "dx" , "dy" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "font-family" , "font-size" , "font-style" , "font-weight" , "id" , "mask" , "opacity" , "requiredFeatures" , "rotate" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "systemLanguage" , "text-anchor" , "textLength" , "transform" , "x" , "xml:space" , "y" ] ,
2010-02-06 01:14:42 -06:00
"use" : [ "class" , "clip-path" , "clip-rule" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "height" , "id" , "mask" , "stroke" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke-width" , "style" , "transform" , "width" , "x" , "xlink:href" , "y" ] ,
2010-02-10 00:11:14 -06:00
// MathML Elements
2010-02-22 00:05:52 -06:00
"annotation" : [ "encoding" ] ,
2010-02-06 01:14:42 -06:00
"annotation-xml" : [ "encoding" ] ,
"maction" : [ "actiontype" , "other" , "selection" ] ,
"math" : [ "class" , "id" , "display" , "xmlns" ] ,
2010-06-22 08:26:15 -05:00
"menclose" : [ "notation" ] ,
2010-02-06 01:14:42 -06:00
"merror" : [ ] ,
"mfrac" : [ "linethickness" ] ,
"mi" : [ "mathvariant" ] ,
"mmultiscripts" : [ ] ,
"mn" : [ ] ,
"mo" : [ "fence" , "lspace" , "maxsize" , "minsize" , "rspace" , "stretchy" ] ,
"mover" : [ ] ,
"mpadded" : [ "lspace" , "width" ] ,
"mphantom" : [ ] ,
"mprescripts" : [ ] ,
"mroot" : [ ] ,
"mrow" : [ "xlink:href" , "xlink:type" , "xmlns:xlink" ] ,
"mspace" : [ "depth" , "height" , "width" ] ,
"msqrt" : [ ] ,
"mstyle" : [ "displaystyle" , "mathbackground" , "mathcolor" , "mathvariant" , "scriptlevel" ] ,
"msub" : [ ] ,
"msubsup" : [ ] ,
"msup" : [ ] ,
"mtable" : [ "align" , "columnalign" , "columnlines" , "columnspacing" , "displaystyle" , "equalcolumns" , "equalrows" , "frame" , "rowalign" , "rowlines" , "rowspacing" , "width" ] ,
"mtd" : [ "columnalign" , "columnspan" , "rowalign" , "rowspan" ] ,
"mtext" : [ ] ,
"mtr" : [ "columnalign" , "rowalign" ] ,
"munder" : [ ] ,
"munderover" : [ ] ,
"none" : [ ] ,
"semantics" : [ ]
2010-02-05 21:36:35 -06:00
} ,
2010-06-22 08:26:15 -05:00
// Interface strings, usually for title elements
2010-02-05 21:36:35 -06:00
uiStrings = {
2010-04-28 00:22:49 -05:00
"pathNodeTooltip" : "Drag node to move it. Double-click node to change segment type" ,
"pathCtrlPtTooltip" : "Drag control point to adjust curve properties" ,
"exportNoBlur" : "Blurred elements will appear as un-blurred" ,
"exportNoImage" : "Image elements will not appear" ,
"exportNoforeignObject" : "foreignObject elements will not appear" ,
"exportNoDashArray" : "Strokes will appear filled" ,
"exportNoText" : "Text may not appear as expected"
2010-02-05 21:36:35 -06:00
} ,
2010-06-22 08:26:15 -05:00
// Default configuration options
2010-03-12 03:50:23 -06:00
curConfig = {
2010-03-15 11:13:22 -05:00
show _outside _canvas : true ,
dimensions : [ 640 , 480 ]
2010-07-20 07:59:47 -05:00
} ;
2010-03-12 03:50:23 -06:00
2010-09-10 15:19:23 -05:00
// Much faster than running getBBox() every time
var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use' ;
// var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Update config with new one if given
if ( config ) {
$ . extend ( curConfig , config ) ;
}
// Static class for various utility functions
var Utils = this . Utils = function ( ) {
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" ;
return {
// Function: Utils.toXml
// Converts characters in a string to XML-friendly entities.
//
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
"toXml" : function ( str ) {
return $ ( '<p/>' ) . text ( str ) . html ( ) ;
} ,
// Function: Utils.fromXml
// Converts XML entities in a string to single characters.
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
"fromXml" : function ( str ) {
return $ ( '<p/>' ) . html ( str ) . text ( ) ;
} ,
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
// schiller: Removed string concatenation in favour of Array.join() optimization,
// also precalculate the size of the array needed.
// Function: Utils.encode64
// Converts a string to base64
"encode64" : function ( input ) {
// base64 strings are 4/3 larger than the original string
// input = Utils.encodeUTF8(input); // convert non-ASCII characters
input = Utils . convertToXMLReferences ( input ) ;
if ( window . btoa ) return window . btoa ( input ) ; // Use native if available
var output = new Array ( Math . floor ( ( input . length + 2 ) / 3 ) * 4 ) ;
var chr1 , chr2 , chr3 ;
var enc1 , enc2 , enc3 , enc4 ;
var i = 0 , p = 0 ;
do {
chr1 = input . charCodeAt ( i ++ ) ;
chr2 = input . charCodeAt ( i ++ ) ;
chr3 = input . charCodeAt ( i ++ ) ;
enc1 = chr1 >> 2 ;
enc2 = ( ( chr1 & 3 ) << 4 ) | ( chr2 >> 4 ) ;
enc3 = ( ( chr2 & 15 ) << 2 ) | ( chr3 >> 6 ) ;
enc4 = chr3 & 63 ;
if ( isNaN ( chr2 ) ) {
enc3 = enc4 = 64 ;
} else if ( isNaN ( chr3 ) ) {
enc4 = 64 ;
}
output [ p ++ ] = _keyStr . charAt ( enc1 ) ;
output [ p ++ ] = _keyStr . charAt ( enc2 ) ;
output [ p ++ ] = _keyStr . charAt ( enc3 ) ;
output [ p ++ ] = _keyStr . charAt ( enc4 ) ;
} while ( i < input . length ) ;
return output . join ( '' ) ;
} ,
// Function: Utils.decode64
// Converts a string from base64
"decode64" : function ( input ) {
if ( window . atob ) return window . atob ( input ) ;
var output = "" ;
var chr1 , chr2 , chr3 = "" ;
var enc1 , enc2 , enc3 , enc4 = "" ;
var i = 0 ;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input . replace ( /[^A-Za-z0-9\+\/\=]/g , "" ) ;
do {
enc1 = _keyStr . indexOf ( input . charAt ( i ++ ) ) ;
enc2 = _keyStr . indexOf ( input . charAt ( i ++ ) ) ;
enc3 = _keyStr . indexOf ( input . charAt ( i ++ ) ) ;
enc4 = _keyStr . indexOf ( input . charAt ( i ++ ) ) ;
chr1 = ( enc1 << 2 ) | ( enc2 >> 4 ) ;
chr2 = ( ( enc2 & 15 ) << 4 ) | ( enc3 >> 2 ) ;
chr3 = ( ( enc3 & 3 ) << 6 ) | enc4 ;
output = output + String . fromCharCode ( chr1 ) ;
if ( enc3 != 64 ) {
output = output + String . fromCharCode ( chr2 ) ;
}
if ( enc4 != 64 ) {
output = output + String . fromCharCode ( chr3 ) ;
}
chr1 = chr2 = chr3 = "" ;
enc1 = enc2 = enc3 = enc4 = "" ;
} while ( i < input . length ) ;
return unescape ( output ) ;
} ,
// Currently not being used, so commented out for now
// based on http://phpjs.org/functions/utf8_encode:577
// codedread:does not seem to work with webkit-based browsers on OSX
// "encodeUTF8": function(input) {
// //return unescape(encodeURIComponent(input)); //may or may not work
// var output = '';
// for (var n = 0; n < input.length; n++){
// var c = input.charCodeAt(n);
// if (c < 128) {
// output += input[n];
// }
// else if (c > 127) {
// if (c < 2048){
// output += String.fromCharCode((c >> 6) | 192);
// }
// else {
// output += String.fromCharCode((c >> 12) | 224) + String.fromCharCode((c >> 6) & 63 | 128);
// }
// output += String.fromCharCode((c & 63) | 128);
// }
// }
// return output;
// },
// Function: Utils.convertToXMLReferences
// Converts a string to use XML references
"convertToXMLReferences" : function ( input ) {
var output = '' ;
for ( var n = 0 ; n < input . length ; n ++ ) {
var c = input . charCodeAt ( n ) ;
if ( c < 128 ) {
output += input [ n ] ;
}
else if ( c > 127 ) {
output += ( "&#" + c + ";" ) ;
}
}
return output ;
} ,
// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
"rectsIntersect" : function ( r1 , r2 ) {
return r2 . x < ( r1 . x + r1 . width ) &&
( r2 . x + r2 . width ) > r1 . x &&
r2 . y < ( r1 . y + r1 . height ) &&
( r2 . y + r2 . height ) > r1 . y ;
} ,
2010-06-22 08:26:15 -05:00
2010-07-20 07:59:47 -05:00
// Function: snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
"snapToAngle" : function ( x1 , y1 , x2 , y2 ) {
var snap = Math . PI / 4 ; // 45 degrees
var dx = x2 - x1 ;
var dy = y2 - y1 ;
var angle = Math . atan2 ( dy , dx ) ;
var dist = Math . sqrt ( dx * dx + dy * dy ) ;
var snapangle = Math . round ( angle / snap ) * snap ;
var x = x1 + dist * Math . cos ( snapangle ) ;
var y = y1 + dist * Math . sin ( snapangle ) ;
//console.log(x1,y1,x2,y2,x,y,angle)
return { x : x , y : y , a : snapangle } ;
} ,
2010-09-10 15:19:23 -05:00
// Function: snapToGrid
// round value to for snapping
"snapToGrid" : function ( value ) {
var stepSize = svgEditor . curConfig . snappingStep ;
value = Math . round ( value / stepSize ) * stepSize ;
return value ;
} ,
2010-07-20 07:59:47 -05:00
// Function: text2xml
// Cross-browser compatible method of converting a string to an XML tree
// found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
"text2xml" : function ( sXML ) {
if ( sXML . indexOf ( '<svg:svg' ) !== - 1 ) {
sXML = sXML . replace ( /<(\/?)svg:/g , '<$1' ) . replace ( 'xmlns:svg' , 'xmlns' ) ;
}
var out ;
try {
2010-09-10 15:19:23 -05:00
var dXML = ( window . DOMParser ) ? new DOMParser ( ) : new ActiveXObject ( "Microsoft.XMLDOM" ) ;
2010-07-20 07:59:47 -05:00
dXML . async = false ;
} catch ( e ) {
throw new Error ( "XML Parser could not be instantiated" ) ;
} ;
try {
2010-09-10 15:19:23 -05:00
if ( dXML . loadXML ) out = ( dXML . loadXML ( sXML ) ) ? dXML : false ;
2010-07-20 07:59:47 -05:00
else out = dXML . parseFromString ( sXML , "text/xml" ) ;
}
catch ( e ) { throw new Error ( "Error parsing XML string" ) ; } ;
return out ;
}
}
} ( ) ;
2010-09-10 15:19:23 -05:00
var elData = $ . data ;
2010-06-22 08:26:15 -05:00
// TODO: declare the variables and set them as null, then move this setup stuff to
// an initialization function - probably just use clear()
var canvas = this ,
// Namespace constants
svgns = "http://www.w3.org/2000/svg" ,
xlinkns = "http://www.w3.org/1999/xlink" ,
xmlns = "http://www.w3.org/XML/1998/namespace" ,
xmlnsns = "http://www.w3.org/2000/xmlns/" , // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
se _ns = "http://svg-edit.googlecode.com" ,
htmlns = "http://www.w3.org/1999/xhtml" ,
mathns = "http://www.w3.org/1998/Math/MathML" ,
// Map of units, those set to 0 are updated later based on calculations
unit _types = { 'em' : 0 , 'ex' : 0 , 'px' : 1 , 'cm' : 35.43307 , 'mm' : 3.543307 , 'in' : 90 , 'pt' : 1.25 , 'pc' : 15 , '%' : 0 } ,
//nonce to uniquify id's
nonce = Math . floor ( Math . random ( ) * 100001 ) ,
// Boolean to indicate whether or not IDs given to elements should be random
randomize _ids = false ,
2010-02-05 21:36:35 -06:00
2010-06-22 08:26:15 -05:00
// "document" element associated with the container (same as window.document using default svg-editor.js)
svgdoc = container . ownerDocument ,
// Array with width/height of canvas
2010-09-10 15:19:23 -05:00
dimensions = curConfig . dimensions ;
if ( $ . browser . msie ) {
var svgroot = document . createElementNS ( svgns , 'svg' ) ;
svgroot . id = 'svgroot' ;
svgroot . setAttribute ( 'width' , dimensions [ 0 ] ) ;
svgroot . setAttribute ( 'height' , dimensions [ 1 ] ) ;
} else {
// Create Root SVG element. This is a container for the document being edited, not the document itself.
var svgroot = svgdoc . importNode ( Utils . text2xml ( '<svg id="svgroot" xmlns="' + svgns + '" xlinkns="' + xlinkns + '" ' +
'width="' + dimensions [ 0 ] + '" height="' + dimensions [ 1 ] + '" x="' + dimensions [ 0 ] + '" y="' + dimensions [ 1 ] + '" overflow="visible">' +
'<defs>' +
'<filter id="canvashadow" filterUnits="objectBoundingBox">' +
'<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>' +
'<feOffset in="blur" dx="5" dy="5" result="offsetBlur"/>' +
'<feMerge>' +
'<feMergeNode in="offsetBlur"/>' +
'<feMergeNode in="SourceGraphic"/>' +
'</feMerge>' +
'</filter>' +
'</defs>' +
'</svg>' ) . documentElement , true ) ;
}
2010-06-22 08:26:15 -05:00
container . appendChild ( svgroot ) ;
// The actual element that represents the final output SVG element
var svgcontent = svgdoc . createElementNS ( svgns , "svg" ) ;
$ ( svgcontent ) . attr ( {
id : 'svgcontent' ,
width : dimensions [ 0 ] ,
height : dimensions [ 1 ] ,
x : dimensions [ 0 ] ,
y : dimensions [ 1 ] ,
overflow : curConfig . show _outside _canvas ? 'visible' : 'hidden' ,
xmlns : svgns ,
"xmlns:se" : se _ns ,
"xmlns:xlink" : xlinkns
} ) . appendTo ( svgroot ) ;
// Set nonce if randomize_ids = true
if ( randomize _ids ) svgcontent . setAttributeNS ( se _ns , 'se:nonce' , nonce ) ;
// map namespace URIs to prefixes
var nsMap = { } ;
nsMap [ xlinkns ] = 'xlink' ;
nsMap [ xmlns ] = 'xml' ;
nsMap [ xmlnsns ] = 'xmlns' ;
nsMap [ se _ns ] = 'se' ;
nsMap [ htmlns ] = 'xhtml' ;
nsMap [ mathns ] = 'mathml' ;
// map prefixes to namespace URIs
var nsRevMap = { } ;
$ . each ( nsMap , function ( key , value ) {
nsRevMap [ value ] = key ;
} ) ;
// Produce a Namespace-aware version of svgWhitelist
var svgWhiteListNS = { } ;
$ . each ( svgWhiteList , function ( elt , atts ) {
var attNS = { } ;
$ . each ( atts , function ( i , att ) {
if ( att . indexOf ( ':' ) != - 1 ) {
var v = att . split ( ':' ) ;
attNS [ v [ 1 ] ] = nsRevMap [ v [ 0 ] ] ;
} else {
attNS [ att ] = att == 'xmlns' ? xmlnsns : null ;
}
} ) ;
svgWhiteListNS [ elt ] = attNS ;
} ) ;
// Animation element to change the opacity of any newly created element
var opac _ani = document . createElementNS ( svgns , 'animate' ) ;
$ ( opac _ani ) . attr ( {
attributeName : 'opacity' ,
begin : 'indefinite' ,
dur : 1 ,
fill : 'freeze'
} ) . appendTo ( svgroot ) ;
2010-07-20 07:59:47 -05:00
// Group: Unit conversion functions
// Set the scope for these functions
2010-06-22 08:26:15 -05:00
var convertToNum , convertToUnit , setUnitAttr ;
( function ( ) {
var w _attrs = [ 'x' , 'x1' , 'cx' , 'rx' , 'width' ] ;
var h _attrs = [ 'y' , 'y1' , 'cy' , 'ry' , 'height' ] ;
var unit _attrs = $ . merge ( [ 'r' , 'radius' ] , w _attrs ) ;
$ . merge ( unit _attrs , h _attrs ) ;
2010-07-20 07:59:47 -05:00
// Function: convertToNum
2010-06-22 08:26:15 -05:00
// Converts given values to numbers. Attributes must be supplied in
// case a percentage is given
2010-07-20 07:59:47 -05:00
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
2010-06-22 08:26:15 -05:00
convertToNum = function ( attr , val ) {
// Return a number if that's what it already is
if ( ! isNaN ( val ) ) return val - 0 ;
if ( val . substr ( - 1 ) === '%' ) {
// Deal with percentage, depends on attribute
var num = val . substr ( 0 , val . length - 1 ) / 100 ;
2010-07-20 07:59:47 -05:00
var res = getResolution ( ) ;
2010-06-22 08:26:15 -05:00
if ( $ . inArray ( attr , w _attrs ) !== - 1 ) {
return num * res . w ;
} else if ( $ . inArray ( attr , h _attrs ) !== - 1 ) {
return num * res . h ;
} else {
return num * Math . sqrt ( ( res . w * res . w ) + ( res . h * res . h ) ) / Math . sqrt ( 2 ) ;
}
} else {
var unit = val . substr ( - 2 ) ;
var num = val . substr ( 0 , val . length - 2 ) ;
// Note that this multiplication turns the string into a number
return num * unit _types [ unit ] ;
}
} ;
2010-07-20 07:59:47 -05:00
// Function: setUnitAttr
// Sets an element's attribute based on the unit in its current value.
//
// Parameters:
// elem - DOM element to be changed
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to convert
2010-06-22 08:26:15 -05:00
setUnitAttr = function ( elem , attr , val ) {
if ( ! isNaN ( val ) ) {
// New value is a number, so check currently used unit
var old _val = elem . getAttribute ( attr ) ;
if ( old _val !== null && isNaN ( old _val ) ) {
// Old value was a number, so get unit, then convert
var unit ;
if ( old _val . substr ( - 1 ) === '%' ) {
2010-07-20 07:59:47 -05:00
var res = getResolution ( ) ;
2010-06-22 08:26:15 -05:00
unit = '%' ;
val *= 100 ;
if ( $ . inArray ( attr , w _attrs ) !== - 1 ) {
val = val / res . w ;
} else if ( $ . inArray ( attr , h _attrs ) !== - 1 ) {
val = val / res . h ;
} else {
return val / Math . sqrt ( ( res . w * res . w ) + ( res . h * res . h ) ) / Math . sqrt ( 2 ) ;
}
} else {
unit = old _val . substr ( - 2 ) ;
val = val / unit _types [ unit ] ;
}
val += unit ;
}
}
elem . setAttribute ( attr , val ) ;
}
2010-07-20 07:59:47 -05:00
// Function: isValidUnit
// Check if an attribute's value is in a valid format
//
// Parameters:
// attr - String with the name of the attribute associated with the value
// val - String with the attribute value to check
2010-06-22 08:26:15 -05:00
canvas . isValidUnit = function ( attr , val ) {
var valid = false ;
if ( $ . inArray ( attr , unit _attrs ) != - 1 ) {
// True if it's just a number
if ( ! isNaN ( val ) ) {
valid = true ;
} else {
// Not a number, check if it has a valid unit
val = val . toLowerCase ( ) ;
$ . each ( unit _types , function ( unit ) {
if ( valid ) return ;
var re = new RegExp ( '^-?[\\d\\.]+' + unit + '$' ) ;
if ( re . test ( val ) ) valid = true ;
} ) ;
}
} else if ( attr == "id" ) {
// if we're trying to change the id, make sure it's not already present in the doc
// and the id value is valid.
var result = false ;
// because getElem() can throw an exception in the case of an invalid id
// (according to http://www.w3.org/TR/xml-id/ IDs must be a NCName)
// we wrap it in an exception and only return true if the ID was valid and
// not already present
try {
var elem = getElem ( val ) ;
result = ( elem == null ) ;
} catch ( e ) { }
return result ;
} else valid = true ;
return valid ;
}
} ) ( ) ;
// Group: Undo/Redo history management
this . undoCmd = { } ;
// Function: ChangeElementCommand
// History command to make a change to an element.
// Usually an attribute change, but can also be textcontent.
//
// Parameters:
// elem - The DOM element that was changed
// attrs - An object with the attributes to be changed and the values they had *before* the change
// text - An optional string visible to user related to this change
var ChangeElementCommand = this . undoCmd . changeElement = function ( elem , attrs , text ) {
2010-02-05 21:36:35 -06:00
this . elem = elem ;
this . text = text ? ( "Change " + elem . tagName + " " + text ) : ( "Change " + elem . tagName ) ;
this . newValues = { } ;
this . oldValues = attrs ;
for ( var attr in attrs ) {
if ( attr == "#text" ) this . newValues [ attr ] = elem . textContent ;
2010-09-10 15:19:23 -05:00
else if ( attr == "#href" ) this . newValues [ attr ] = getHref ( elem ) ;
2010-02-05 21:36:35 -06:00
else this . newValues [ attr ] = elem . getAttribute ( attr ) ;
}
2010-06-22 08:26:15 -05:00
// Function: ChangeElementCommand.apply
// Performs the stored change action
2010-02-05 21:36:35 -06:00
this . apply = function ( ) {
var bChangedTransform = false ;
for ( var attr in this . newValues ) {
if ( this . newValues [ attr ] ) {
if ( attr == "#text" ) this . elem . textContent = this . newValues [ attr ] ;
2010-09-10 15:19:23 -05:00
else if ( attr == "#href" ) setHref ( this . elem , this . newValues [ attr ] )
2010-02-05 21:36:35 -06:00
else this . elem . setAttribute ( attr , this . newValues [ attr ] ) ;
}
else {
if ( attr == "#text" ) this . elem . textContent = "" ;
else {
this . elem . setAttribute ( attr , "" ) ;
this . elem . removeAttribute ( attr ) ;
}
}
2010-06-03 07:13:37 -05:00
2010-02-05 21:36:35 -06:00
if ( attr == "transform" ) { bChangedTransform = true ; }
2010-06-03 07:13:37 -05:00
else if ( attr == "stdDeviation" ) { canvas . setBlurOffsets ( this . elem . parentNode , this . newValues [ attr ] ) ; }
2010-02-05 21:36:35 -06:00
}
// relocate rotational transform, if necessary
if ( ! bChangedTransform ) {
2010-07-20 07:59:47 -05:00
var angle = getRotationAngle ( elem ) ;
2010-02-05 21:36:35 -06:00
if ( angle ) {
var bbox = elem . getBBox ( ) ;
var cx = bbox . x + bbox . width / 2 ,
cy = bbox . y + bbox . height / 2 ;
var rotate = [ "rotate(" , angle , " " , cx , "," , cy , ")" ] . join ( '' ) ;
if ( rotate != elem . getAttribute ( "transform" ) ) {
elem . setAttribute ( "transform" , rotate ) ;
}
}
}
// if we are changing layer names, re-identify all layers
if ( this . elem . tagName == "title" && this . elem . parentNode . parentNode == svgcontent ) {
identifyLayers ( ) ;
}
return true ;
} ;
2010-06-22 08:26:15 -05:00
// Function: ChangeElementCommand.unapply
// Reverses the stored change action
2010-02-05 21:36:35 -06:00
this . unapply = function ( ) {
var bChangedTransform = false ;
for ( var attr in this . oldValues ) {
if ( this . oldValues [ attr ] ) {
if ( attr == "#text" ) this . elem . textContent = this . oldValues [ attr ] ;
2010-09-10 15:19:23 -05:00
else if ( attr == "#href" ) setHref ( this . elem , this . oldValues [ attr ] ) ;
2010-02-05 21:36:35 -06:00
else this . elem . setAttribute ( attr , this . oldValues [ attr ] ) ;
2010-06-03 07:13:37 -05:00
if ( attr == "stdDeviation" ) canvas . setBlurOffsets ( this . elem . parentNode , this . oldValues [ attr ] ) ;
2010-02-05 21:36:35 -06:00
}
else {
if ( attr == "#text" ) this . elem . textContent = "" ;
else this . elem . removeAttribute ( attr ) ;
}
if ( attr == "transform" ) { bChangedTransform = true ; }
}
// relocate rotational transform, if necessary
if ( ! bChangedTransform ) {
2010-07-20 07:59:47 -05:00
var angle = getRotationAngle ( elem ) ;
2010-02-05 21:36:35 -06:00
if ( angle ) {
var bbox = elem . getBBox ( ) ;
var cx = bbox . x + bbox . width / 2 ,
cy = bbox . y + bbox . height / 2 ;
var rotate = [ "rotate(" , angle , " " , cx , "," , cy , ")" ] . join ( '' ) ;
if ( rotate != elem . getAttribute ( "transform" ) ) {
elem . setAttribute ( "transform" , rotate ) ;
}
}
}
// if we are changing layer names, re-identify all layers
if ( this . elem . tagName == "title" && this . elem . parentNode . parentNode == svgcontent ) {
identifyLayers ( ) ;
}
2010-06-16 22:46:12 -05:00
// Remove transformlist to prevent confusion that causes bugs like 575.
if ( svgTransformLists [ this . elem . id ] ) {
delete svgTransformLists [ this . elem . id ] ;
}
2010-02-05 21:36:35 -06:00
return true ;
} ;
2010-06-22 08:26:15 -05:00
// Function: ChangeElementCommand.elements
// Returns array with element associated with this command
2010-02-05 21:36:35 -06:00
this . elements = function ( ) { return [ this . elem ] ; }
}
2010-06-22 08:26:15 -05:00
// Function: InsertElementCommand
// History command for an element that was added to the DOM
//
// Parameters:
// elem - The newly added DOM element
// text - An optional string visible to user related to this change
var InsertElementCommand = this . undoCmd . insertElement = function ( elem , text ) {
2010-02-05 21:36:35 -06:00
this . elem = elem ;
this . text = text || ( "Create " + elem . tagName ) ;
this . parent = elem . parentNode ;
2010-06-22 08:26:15 -05:00
// Function: InsertElementCommand.apply
// Re-Inserts the new element
2010-02-05 21:36:35 -06:00
this . apply = function ( ) {
this . elem = this . parent . insertBefore ( this . elem , this . elem . nextSibling ) ;
if ( this . parent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: InsertElementCommand.unapply
// Removes the element
2010-02-05 21:36:35 -06:00
this . unapply = function ( ) {
this . parent = this . elem . parentNode ;
this . elem = this . elem . parentNode . removeChild ( this . elem ) ;
if ( this . parent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: InsertElementCommand.elements
// Returns array with element associated with this command
2010-02-05 21:36:35 -06:00
this . elements = function ( ) { return [ this . elem ] ; } ;
}
2010-06-22 08:26:15 -05:00
// Function: RemoveElementCommand
// History command for an element removed from the DOM
//
// Parameters:
// elem - The removed DOM element
// parent - The DOM element's parent
// text - An optional string visible to user related to this change
var RemoveElementCommand = this . undoCmd . removeElement = function ( elem , parent , text ) {
2010-02-05 21:36:35 -06:00
this . elem = elem ;
this . text = text || ( "Delete " + elem . tagName ) ;
this . parent = parent ;
2010-06-22 08:26:15 -05:00
// Function: RemoveElementCommand.apply
// Re-removes the new element
2010-06-16 22:46:12 -05:00
this . apply = function ( ) {
if ( svgTransformLists [ this . elem . id ] ) {
delete svgTransformLists [ this . elem . id ] ;
}
2010-02-05 21:36:35 -06:00
this . parent = this . elem . parentNode ;
this . elem = this . parent . removeChild ( this . elem ) ;
if ( this . parent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: RemoveElementCommand.unapply
// Re-adds the new element
2010-02-05 21:36:35 -06:00
this . unapply = function ( ) {
2010-06-16 22:46:12 -05:00
if ( svgTransformLists [ this . elem . id ] ) {
delete svgTransformLists [ this . elem . id ] ;
}
2010-09-10 15:19:23 -05:00
this . elem = this . parent . insertBefore ( this . elem , this . elem . nextSibling ) ;
2010-02-05 21:36:35 -06:00
if ( this . parent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: RemoveElementCommand.elements
// Returns array with element associated with this command
2010-02-05 21:36:35 -06:00
this . elements = function ( ) { return [ this . elem ] ; } ;
// special hack for webkit: remove this element's entry in the svgTransformLists map
if ( svgTransformLists [ elem . id ] ) {
delete svgTransformLists [ elem . id ] ;
}
}
2010-06-22 08:26:15 -05:00
// Function: MoveElementCommand
// History command for an element that had its DOM position changed
//
// Parameters:
// elem - The DOM element that was moved
// oldNextSibling - The element's next sibling before it was moved
// oldParent - The element's parent before it was moved
// text - An optional string visible to user related to this change
var MoveElementCommand = this . undoCmd . moveElement = function ( elem , oldNextSibling , oldParent , text ) {
2010-02-05 21:36:35 -06:00
this . elem = elem ;
this . text = text ? ( "Move " + elem . tagName + " to " + text ) : ( "Move " + elem . tagName ) ;
this . oldNextSibling = oldNextSibling ;
this . oldParent = oldParent ;
this . newNextSibling = elem . nextSibling ;
this . newParent = elem . parentNode ;
2010-06-22 08:26:15 -05:00
// Function: MoveElementCommand.unapply
// Re-positions the element
2010-02-05 21:36:35 -06:00
this . apply = function ( ) {
this . elem = this . newParent . insertBefore ( this . elem , this . newNextSibling ) ;
if ( this . newParent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: MoveElementCommand.unapply
// Positions the element back to its original location
2010-02-05 21:36:35 -06:00
this . unapply = function ( ) {
this . elem = this . oldParent . insertBefore ( this . elem , this . oldNextSibling ) ;
if ( this . oldParent == svgcontent ) {
identifyLayers ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: MoveElementCommand.elements
// Returns array with element associated with this command
2010-02-05 21:36:35 -06:00
this . elements = function ( ) { return [ this . elem ] ; } ;
}
// TODO: create a 'typing' command object that tracks changes in text
// if a new Typing command is created and the top command on the stack is also a Typing
// and they both affect the same element, then collapse the two commands into one
2010-06-22 08:26:15 -05:00
// Function: BatchCommand
// History command that can contain/execute multiple other commands
//
// Parameters:
// text - An optional string visible to user related to this change
var BatchCommand = this . undoCmd . batch = function ( text ) {
2010-02-05 21:36:35 -06:00
this . text = text || "Batch Command" ;
this . stack = [ ] ;
2010-06-22 08:26:15 -05:00
// Function: BatchCommand.apply
// Runs "apply" on all subcommands
2010-02-05 21:36:35 -06:00
this . apply = function ( ) {
var len = this . stack . length ;
for ( var i = 0 ; i < len ; ++ i ) {
this . stack [ i ] . apply ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: BatchCommand.unapply
// Runs "unapply" on all subcommands
2010-02-05 21:36:35 -06:00
this . unapply = function ( ) {
for ( var i = this . stack . length - 1 ; i >= 0 ; i -- ) {
this . stack [ i ] . unapply ( ) ;
}
} ;
2010-06-22 08:26:15 -05:00
// Function: BatchCommand.elements
// Iterate through all our subcommands and returns all the elements we are changing
2010-02-05 21:36:35 -06:00
this . elements = function ( ) {
var elems = [ ] ;
var cmd = this . stack . length ;
while ( cmd -- ) {
var thisElems = this . stack [ cmd ] . elements ( ) ;
var elem = thisElems . length ;
while ( elem -- ) {
if ( elems . indexOf ( thisElems [ elem ] ) == - 1 ) elems . push ( thisElems [ elem ] ) ;
}
}
return elems ;
} ;
2010-06-22 08:26:15 -05:00
// Function: BatchCommand.addSubCommand
// Adds a given command to the history stack
2010-07-20 07:59:47 -05:00
//
2010-06-22 08:26:15 -05:00
// Parameters:
// cmd - The undo command object to add
2010-02-05 21:36:35 -06:00
this . addSubCommand = function ( cmd ) { this . stack . push ( cmd ) ; } ;
2010-06-22 08:26:15 -05:00
// Function: BatchCommand.isEmpty
// Returns a boolean indicating whether or not the batch command is empty
2010-02-05 21:36:35 -06:00
this . isEmpty = function ( ) { return this . stack . length == 0 ; } ;
}
2010-06-22 08:26:15 -05:00
// Set scope for these undo functions
var resetUndoStack , addCommandToHistory ;
2010-02-05 21:36:35 -06:00
2010-06-22 08:26:15 -05:00
// Undo/redo stack related functions
( function ( c ) {
var undoStackPointer = 0 ,
undoStack = [ ] ;
// Function: resetUndoStack
// Resets the undo stack, effectively clearing the undo/redo history
resetUndoStack = function ( ) {
undoStack = [ ] ;
undoStackPointer = 0 ;
} ;
c . undoMgr = {
// Function: undoMgr.getUndoStackSize
// Returns:
// Integer with the current size of the undo history stack
getUndoStackSize : function ( ) { return undoStackPointer ; } ,
// Function: undoMgr.getRedoStackSize
// Returns:
// Integer with the current size of the redo history stack
getRedoStackSize : function ( ) { return undoStack . length - undoStackPointer ; } ,
// Function: undoMgr.getNextUndoCommandText
// Returns:
// String associated with the next undo command
getNextUndoCommandText : function ( ) {
if ( undoStackPointer > 0 )
return undoStack [ undoStackPointer - 1 ] . text ;
return "" ;
} ,
// Function: undoMgr.getNextRedoCommandText
// Returns:
// String associated with the next redo command
getNextRedoCommandText : function ( ) {
if ( undoStackPointer < undoStack . length )
return undoStack [ undoStackPointer ] . text ;
return "" ;
} ,
// Function: undoMgr.undo
// Performs an undo step
undo : function ( ) {
if ( undoStackPointer > 0 ) {
c . clearSelection ( ) ;
var cmd = undoStack [ -- undoStackPointer ] ;
cmd . unapply ( ) ;
pathActions . clear ( ) ;
call ( "changed" , cmd . elements ( ) ) ;
}
} ,
// Function: undoMgr.redo
// Performs a redo step
redo : function ( ) {
if ( undoStackPointer < undoStack . length && undoStack . length > 0 ) {
c . clearSelection ( ) ;
var cmd = undoStack [ undoStackPointer ++ ] ;
cmd . apply ( ) ;
pathActions . clear ( ) ;
call ( "changed" , cmd . elements ( ) ) ;
}
}
} ;
// Function: addCommandToHistory
// Adds a command object to the undo history stack
//
// Parameters:
// cmd - The command object to add
addCommandToHistory = c . undoCmd . add = function ( cmd ) {
// FIXME: we MUST compress consecutive text changes to the same element
// (right now each keystroke is saved as a separate command that includes the
// entire text contents of the text element)
// TODO: consider limiting the history that we store here (need to do some slicing)
// if our stack pointer is not at the end, then we have to remove
// all commands after the pointer and insert the new command
if ( undoStackPointer < undoStack . length && undoStack . length > 0 ) {
undoStack = undoStack . splice ( 0 , undoStackPointer ) ;
}
undoStack . push ( cmd ) ;
undoStackPointer = undoStack . length ;
} ;
} ( canvas ) ) ;
( function ( c ) {
// New functions for refactoring of Undo/Redo
// this is the stack that stores the original values, the elements and
// the attribute name for begin/finish
var undoChangeStackPointer = - 1 ;
var undoableChangeStack = [ ] ;
// Function: beginUndoableChange
// This function tells the canvas to remember the old values of the
// attrName attribute for each element sent in. The elements and values
// are stored on a stack, so the next call to finishUndoableChange() will
// pop the elements and old values off the stack, gets the current values
// from the DOM and uses all of these to construct the undo-able command.
//
// Parameters:
// attrName - The name of the attribute being changed
// elems - Array of DOM elements being changed
c . beginUndoableChange = function ( attrName , elems ) {
var p = ++ undoChangeStackPointer ;
var i = elems . length ;
var oldValues = new Array ( i ) , elements = new Array ( i ) ;
while ( i -- ) {
var elem = elems [ i ] ;
if ( elem == null ) continue ;
elements [ i ] = elem ;
oldValues [ i ] = elem . getAttribute ( attrName ) ;
}
undoableChangeStack [ p ] = { 'attrName' : attrName ,
'oldValues' : oldValues ,
'elements' : elements } ;
} ;
// Function: finishUndoableChange
// This function returns a BatchCommand object which summarizes the
// change since beginUndoableChange was called. The command can then
// be added to the command history
//
// Returns:
// Batch command object with resulting changes
c . finishUndoableChange = function ( ) {
var p = undoChangeStackPointer -- ;
var changeset = undoableChangeStack [ p ] ;
var i = changeset [ 'elements' ] . length ;
var attrName = changeset [ 'attrName' ] ;
var batchCmd = new BatchCommand ( "Change " + attrName ) ;
while ( i -- ) {
var elem = changeset [ 'elements' ] [ i ] ;
if ( elem == null ) continue ;
var changes = { } ;
changes [ attrName ] = changeset [ 'oldValues' ] [ i ] ;
if ( changes [ attrName ] != elem . getAttribute ( attrName ) ) {
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , changes , attrName ) ) ;
}
}
undoableChangeStack [ p ] = null ;
return batchCmd ;
} ;
} ( canvas ) ) ;
// Put SelectorManager in this scope
var SelectorManager ;
( function ( ) {
2010-07-20 07:59:47 -05:00
// Interface: Selector
2010-06-22 08:26:15 -05:00
// Private class for DOM element selection boxes
//
// Parameters:
// id - integer to internally indentify the selector
// elem - DOM element associated with this selector
2010-02-05 21:36:35 -06:00
function Selector ( id , elem ) {
// this is the selector's unique number
this . id = id ;
// this holds a reference to the element for which this selector is being used
this . selectedElement = elem ;
// this is a flag used internally to track whether the selector is being used or not
this . locked = true ;
2010-06-22 08:26:15 -05:00
// Function: Selector.reset
// Used to reset the id and element that the selector is attached to
//
// Parameters:
// e - DOM element associated with this selector
this . reset = function ( e ) {
2010-02-05 21:36:35 -06:00
this . locked = true ;
this . selectedElement = e ;
this . resize ( ) ;
this . selectorGroup . setAttribute ( "display" , "inline" ) ;
} ;
// this holds a reference to the <g> element that holds all visual elements of the selector
this . selectorGroup = addSvgElementFromJson ( { "element" : "g" ,
"attr" : { "id" : ( "selectorGroup" + this . id ) }
} ) ;
// this holds a reference to the path rect
this . selectorRect = this . selectorGroup . appendChild ( addSvgElementFromJson ( {
"element" : "path" ,
"attr" : {
"id" : ( "selectedBox" + this . id ) ,
"fill" : "none" ,
"stroke" : "#22C" ,
"stroke-width" : "1" ,
"stroke-dasharray" : "5,5" ,
// need to specify this so that the rect is not selectable
"style" : "pointer-events:none"
}
} ) ) ;
2010-09-10 15:19:23 -05:00
// this holds a reference to the grip coordinates for this selector
this . gripCoords = { "nw" : null ,
"n" : null ,
"ne" : null ,
"e" : null ,
"se" : null ,
"s" : null ,
"sw" : null ,
"w" : null
} ;
2010-02-05 21:36:35 -06:00
2010-06-22 08:26:15 -05:00
// Function: Selector.showGrips
// Show the resize grips of this selector
//
// Parameters:
// show - boolean indicating whether grips should be shown or not
2010-02-05 21:36:35 -06:00
this . showGrips = function ( show ) {
// TODO: use suspendRedraw() here
var bShow = show ? "inline" : "none" ;
2010-09-10 15:19:23 -05:00
selectorManager . selectorGripsGroup . setAttribute ( "display" , bShow ) ;
2010-02-05 21:36:35 -06:00
var elem = this . selectedElement ;
2010-09-10 15:19:23 -05:00
if ( elem && show ) {
this . selectorGroup . appendChild ( selectorManager . selectorGripsGroup ) ;
this . updateGripCursors ( getRotationAngle ( elem ) ) ;
2010-02-05 21:36:35 -06:00
}
} ;
2010-06-22 08:26:15 -05:00
// Function: Selector.updateGripCursors
2010-02-05 21:36:35 -06:00
// Updates cursors for corner grips on rotation so arrows point the right way
2010-06-22 08:26:15 -05:00
//
// Parameters:
// angle - Float indicating current rotation angle in degrees
2010-02-05 21:36:35 -06:00
this . updateGripCursors = function ( angle ) {
var dir _arr = [ ] ;
var steps = Math . round ( angle / 45 ) ;
if ( steps < 0 ) steps += 8 ;
2010-09-10 15:19:23 -05:00
for ( var dir in selectorManager . selectorGrips ) {
2010-02-05 21:36:35 -06:00
dir _arr . push ( dir ) ;
}
while ( steps > 0 ) {
dir _arr . push ( dir _arr . shift ( ) ) ;
steps -- ;
}
var i = 0 ;
2010-09-10 15:19:23 -05:00
for ( var dir in selectorManager . selectorGrips ) {
selectorManager . selectorGrips [ dir ] . setAttribute ( 'style' , ( "cursor:" + dir _arr [ i ] + "-resize" ) ) ;
2010-02-05 21:36:35 -06:00
i ++ ;
} ;
} ;
2010-06-22 08:26:15 -05:00
// Function: Selector.resize
// Updates the selector to match the element's size
2010-02-05 21:36:35 -06:00
this . resize = function ( ) {
var selectedBox = this . selectorRect ,
2010-09-10 15:19:23 -05:00
mgr = selectorManager ,
selectedGrips = mgr . selectorGrips ,
2010-02-05 21:36:35 -06:00
selected = this . selectedElement ,
2010-04-28 00:22:49 -05:00
sw = selected . getAttribute ( "stroke-width" ) ;
var offset = 1 / current _zoom ;
2010-02-05 21:36:35 -06:00
if ( selected . getAttribute ( "stroke" ) != "none" && ! isNaN ( sw ) ) {
2010-04-28 00:22:49 -05:00
offset += ( sw / 2 ) ;
2010-02-05 21:36:35 -06:00
}
if ( selected . tagName == "text" ) {
2010-04-28 00:22:49 -05:00
offset += 2 / current _zoom ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var bbox = getBBox ( selected ) ;
if ( selected . tagName == 'g' && ! $ ( selected ) . data ( 'gsvg' ) ) {
2010-02-05 21:36:35 -06:00
// The bbox for a group does not include stroke vals, so we
// get the bbox based on its children.
2010-07-20 07:59:47 -05:00
var stroked _bbox = getStrokedBBox ( selected . childNodes ) ;
2010-08-13 16:14:19 -05:00
if ( stroked _bbox ) {
$ . each ( bbox , function ( key , val ) {
bbox [ key ] = stroked _bbox [ key ] ;
} ) ;
}
2010-02-05 21:36:35 -06:00
}
// loop and transform our bounding box until we reach our first rotation
2010-04-28 00:22:49 -05:00
var m = getMatrix ( selected ) ;
2010-02-05 21:36:35 -06:00
// This should probably be handled somewhere else, but for now
// it keeps the selection box correctly positioned when zoomed
m . e *= current _zoom ;
m . f *= current _zoom ;
// apply the transforms
2010-04-28 00:22:49 -05:00
var l = bbox . x - offset , t = bbox . y - offset , w = bbox . width + ( offset * 2 ) , h = bbox . height + ( offset * 2 ) ,
2010-02-05 21:36:35 -06:00
bbox = { x : l , y : t , width : w , height : h } ;
// we need to handle temporary transforms too
// if skewed, get its transformed box, then find its axis-aligned bbox
//*
var nbox = transformBox ( l * current _zoom , t * current _zoom , w * current _zoom , h * current _zoom , m ) ,
nbax = nbox . aabox . x ,
nbay = nbox . aabox . y ,
nbaw = nbox . aabox . width ,
nbah = nbox . aabox . height ;
// now if the shape is rotated, un-rotate it
var cx = nbax + nbaw / 2 ,
cy = nbay + nbah / 2 ;
2010-07-20 07:59:47 -05:00
var angle = getRotationAngle ( selected ) ;
2010-02-05 21:36:35 -06:00
if ( angle ) {
var rot = svgroot . createSVGTransform ( ) ;
rot . setRotate ( - angle , cx , cy ) ;
var rotm = rot . matrix ;
nbox . tl = transformPoint ( nbox . tl . x , nbox . tl . y , rotm ) ;
nbox . tr = transformPoint ( nbox . tr . x , nbox . tr . y , rotm ) ;
nbox . bl = transformPoint ( nbox . bl . x , nbox . bl . y , rotm ) ;
nbox . br = transformPoint ( nbox . br . x , nbox . br . y , rotm ) ;
// calculate the axis-aligned bbox
var minx = nbox . tl . x ,
miny = nbox . tl . y ,
maxx = nbox . tl . x ,
maxy = nbox . tl . y ;
minx = Math . min ( minx , Math . min ( nbox . tr . x , Math . min ( nbox . bl . x , nbox . br . x ) ) ) ;
miny = Math . min ( miny , Math . min ( nbox . tr . y , Math . min ( nbox . bl . y , nbox . br . y ) ) ) ;
maxx = Math . max ( maxx , Math . max ( nbox . tr . x , Math . max ( nbox . bl . x , nbox . br . x ) ) ) ;
maxy = Math . max ( maxy , Math . max ( nbox . tr . y , Math . max ( nbox . bl . y , nbox . br . y ) ) ) ;
nbax = minx ;
nbay = miny ;
nbaw = ( maxx - minx ) ;
nbah = ( maxy - miny ) ;
}
var sr _handle = svgroot . suspendRedraw ( 100 ) ;
var dstr = "M" + nbax + "," + nbay
+ " L" + ( nbax + nbaw ) + "," + nbay
+ " " + ( nbax + nbaw ) + "," + ( nbay + nbah )
+ " " + nbax + "," + ( nbay + nbah ) + "z" ;
assignAttributes ( selectedBox , { 'd' : dstr } ) ;
2010-09-10 15:19:23 -05:00
this . gripCoords = {
2010-02-05 21:36:35 -06:00
nw : [ nbax , nbay ] ,
ne : [ nbax + nbaw , nbay ] ,
sw : [ nbax , nbay + nbah ] ,
se : [ nbax + nbaw , nbay + nbah ] ,
n : [ nbax + ( nbaw ) / 2 , nbay ] ,
w : [ nbax , nbay + ( nbah ) / 2 ] ,
e : [ nbax + nbaw , nbay + ( nbah ) / 2 ] ,
s : [ nbax + ( nbaw ) / 2 , nbay + nbah ]
} ;
if ( selected == selectedElements [ 0 ] ) {
2010-09-10 15:19:23 -05:00
for ( var dir in this . gripCoords ) {
var coords = this . gripCoords [ dir ] ;
2010-02-05 21:36:35 -06:00
assignAttributes ( selectedGrips [ dir ] , {
cx : coords [ 0 ] , cy : coords [ 1 ]
} ) ;
} ;
}
if ( angle ) {
this . selectorGroup . setAttribute ( "transform" , "rotate(" + [ angle , cx , cy ] . join ( "," ) + ")" ) ;
}
else {
this . selectorGroup . setAttribute ( "transform" , "" ) ;
}
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
2010-09-10 15:19:23 -05:00
assignAttributes ( mgr . rotateGripConnector , { x1 : nbax + ( nbaw ) / 2 ,
2010-02-05 21:36:35 -06:00
y1 : nbay ,
x2 : nbax + ( nbaw ) / 2 ,
y2 : nbay - 20 } ) ;
2010-09-10 15:19:23 -05:00
assignAttributes ( mgr . rotateGrip , { cx : nbax + ( nbaw ) / 2 ,
2010-02-05 21:36:35 -06:00
cy : nbay - 20 } ) ;
svgroot . unsuspendRedraw ( sr _handle ) ;
} ;
// now initialize the selector
this . reset ( elem ) ;
} ;
2010-07-20 07:59:47 -05:00
// Interface: SelectorManager
// Public class to manage all selector objects (selection boxes)
2010-06-22 08:26:15 -05:00
SelectorManager = function ( ) {
2010-07-20 07:59:47 -05:00
// this will hold the <g> element that contains all selector rects/grips
this . selectorParentGroup = null ;
// this is a special rect that is used for multi-select
this . rubberBandBox = null ;
// this will hold objects of type Selector (see above)
this . selectors = [ ] ;
// this holds a map of SVG elements to their Selector object
this . selectorMap = { } ;
// local reference to this object
var mgr = this ;
// Function: SelectorManager.initGroup
// Resets the parent selector group element
this . initGroup = function ( ) {
// remove old selector parent group if it existed
if ( mgr . selectorParentGroup && mgr . selectorParentGroup . parentNode ) {
mgr . selectorParentGroup . parentNode . removeChild ( mgr . selectorParentGroup ) ;
}
// create parent selector group and add it to svgroot
mgr . selectorParentGroup = svgdoc . createElementNS ( svgns , "g" ) ;
mgr . selectorParentGroup . setAttribute ( "id" , "selectorParentGroup" ) ;
2010-09-10 15:19:23 -05:00
mgr . selectorGripsGroup = svgdoc . createElementNS ( svgns , "g" ) ;
2010-09-13 17:05:37 -05:00
mgr . selectorGripsGroup . setAttribute ( 'display' , 'none' ) ;
2010-07-20 07:59:47 -05:00
svgroot . appendChild ( mgr . selectorParentGroup ) ;
2010-09-10 15:19:23 -05:00
mgr . selectorParentGroup . appendChild ( mgr . selectorGripsGroup ) ;
2010-07-20 07:59:47 -05:00
mgr . selectorMap = { } ;
mgr . selectors = [ ] ;
mgr . rubberBandBox = null ;
2010-09-10 15:19:23 -05:00
// this holds a reference to the grip elements
mgr . selectorGrips = { "nw" : null ,
"n" : null ,
"ne" : null ,
"e" : null ,
"se" : null ,
"s" : null ,
"sw" : null ,
"w" : null
} ;
// add the corner grips
for ( var dir in mgr . selectorGrips ) {
var grip = addSvgElementFromJson ( {
"element" : "circle" ,
"attr" : {
"id" : ( "selectorGrip_resize_" + dir ) ,
"fill" : "#22C" ,
"r" : 4 ,
"style" : ( "cursor:" + dir + "-resize" ) ,
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
"stroke-width" : 2 ,
"pointer-events" : "all"
}
} ) ;
elData ( grip , "dir" , dir ) ;
elData ( grip , "type" , "resize" ) ;
this . selectorGrips [ dir ] = mgr . selectorGripsGroup . appendChild ( grip ) ;
}
// add rotator elems
this . rotateGripConnector = this . selectorGripsGroup . appendChild ( addSvgElementFromJson ( {
"element" : "line" ,
"attr" : {
"id" : ( "selectorGrip_rotateconnector" ) ,
"stroke" : "#22C" ,
"stroke-width" : "1"
}
} ) ) ;
this . rotateGrip = this . selectorGripsGroup . appendChild ( addSvgElementFromJson ( {
"element" : "circle" ,
"attr" : {
"id" : "selectorGrip_rotate" ,
"fill" : "lime" ,
"r" : 4 ,
"stroke" : "#22C" ,
"stroke-width" : 2 ,
"style" : "cursor:url(" + curConfig . imgPath + "rotate.png) 12 12, auto;"
}
} ) ) ;
elData ( this . rotateGrip , "type" , "rotate" ) ;
2010-07-20 07:59:47 -05:00
if ( $ ( "#canvasBackground" ) . length ) return ;
var canvasbg = svgdoc . createElementNS ( svgns , "svg" ) ;
var dims = curConfig . dimensions ;
assignAttributes ( canvasbg , {
'id' : 'canvasBackground' ,
'width' : dims [ 0 ] ,
'height' : dims [ 1 ] ,
'x' : 0 ,
'y' : 0 ,
'overflow' : 'visible' ,
'style' : 'pointer-events:none'
} ) ;
var rect = svgdoc . createElementNS ( svgns , "rect" ) ;
assignAttributes ( rect , {
'width' : '100%' ,
'height' : '100%' ,
'x' : 0 ,
'y' : 0 ,
'stroke-width' : 1 ,
'stroke' : '#000' ,
'fill' : '#FFF' ,
'style' : 'pointer-events:none'
} ) ;
// Both Firefox and WebKit are too slow with this filter region (especially at higher
// zoom levels) and Opera has at least one bug
// if (!window.opera) rect.setAttribute('filter', 'url(#canvashadow)');
canvasbg . appendChild ( rect ) ;
svgroot . insertBefore ( canvasbg , svgcontent ) ;
} ;
2010-06-22 08:26:15 -05:00
2010-07-20 07:59:47 -05:00
// Function: SelectorManager.requestSelector
// Returns the selector based on the given element
//
// Parameters:
// elem - DOM element to get the selector for
this . requestSelector = function ( elem ) {
if ( elem == null ) return null ;
var N = this . selectors . length ;
// if we've already acquired one for this element, return it
if ( typeof ( this . selectorMap [ elem . id ] ) == "object" ) {
this . selectorMap [ elem . id ] . locked = true ;
return this . selectorMap [ elem . id ] ;
}
for ( var i = 0 ; i < N ; ++ i ) {
if ( this . selectors [ i ] && ! this . selectors [ i ] . locked ) {
this . selectors [ i ] . locked = true ;
this . selectors [ i ] . reset ( elem ) ;
this . selectorMap [ elem . id ] = this . selectors [ i ] ;
return this . selectors [ i ] ;
}
}
// if we reached here, no available selectors were found, we create one
this . selectors [ N ] = new Selector ( N , elem ) ;
this . selectorParentGroup . appendChild ( this . selectors [ N ] . selectorGroup ) ;
this . selectorMap [ elem . id ] = this . selectors [ N ] ;
return this . selectors [ N ] ;
} ;
// Function: SelectorManager.releaseSelector
// Removes the selector of the given element (hides selection box)
//
// Parameters:
// elem - DOM element to remove the selector for
this . releaseSelector = function ( elem ) {
if ( elem == null ) return ;
var N = this . selectors . length ,
sel = this . selectorMap [ elem . id ] ;
for ( var i = 0 ; i < N ; ++ i ) {
if ( this . selectors [ i ] && this . selectors [ i ] == sel ) {
if ( sel . locked == false ) {
console . log ( "WARNING! selector was released but was already unlocked" ) ;
}
delete this . selectorMap [ elem . id ] ;
sel . locked = false ;
sel . selectedElement = null ;
sel . showGrips ( false ) ;
2010-06-22 08:26:15 -05:00
2010-07-20 07:59:47 -05:00
// remove from DOM and store reference in JS but only if it exists in the DOM
try {
sel . selectorGroup . setAttribute ( "display" , "none" ) ;
} catch ( e ) { }
2010-06-22 08:26:15 -05:00
2010-07-20 07:59:47 -05:00
break ;
2010-06-22 08:26:15 -05:00
}
2010-07-20 07:59:47 -05:00
}
} ;
// Function: SelectorManager.getRubberBandBox
// Returns the rubberBandBox DOM element. This is the rectangle drawn by the user for selecting/zooming
this . getRubberBandBox = function ( ) {
if ( this . rubberBandBox == null ) {
this . rubberBandBox = this . selectorParentGroup . appendChild (
addSvgElementFromJson ( { "element" : "rect" ,
"attr" : {
"id" : "selectorRubberBand" ,
"fill" : "#22C" ,
"fill-opacity" : 0.15 ,
"stroke" : "#22C" ,
"stroke-width" : 0.5 ,
"display" : "none" ,
"style" : "pointer-events:none"
}
} ) ) ;
}
return this . rubberBandBox ;
} ;
this . initGroup ( ) ;
} ;
} ( ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// **************************************************************************************
// SVGTransformList implementation for Webkit
// These methods do not currently raise any exceptions.
// These methods also do not check that transforms are being inserted or handle if
// a transform is already in the list, etc. This is basically implementing as much
// of SVGTransformList that we need to get the job done.
//
// interface SVGEditTransformList {
// attribute unsigned long numberOfItems;
// void clear ( )
// SVGTransform initialize ( in SVGTransform newItem )
// SVGTransform getItem ( in unsigned long index )
// SVGTransform insertItemBefore ( in SVGTransform newItem, in unsigned long index )
// SVGTransform replaceItem ( in SVGTransform newItem, in unsigned long index )
// SVGTransform removeItem ( in unsigned long index )
// SVGTransform appendItem ( in SVGTransform newItem )
// NOT IMPLEMENTED: SVGTransform createSVGTransformFromMatrix ( in SVGMatrix matrix );
// NOT IMPLEMENTED: SVGTransform consolidate ( );
// }
// **************************************************************************************
var svgTransformLists = { } ;
var SVGEditTransformList = function ( elem ) {
function transformToString ( xform ) {
var m = xform . matrix ,
text = "" ;
switch ( xform . type ) {
case 1 : // MATRIX
text = "matrix(" + [ m . a , m . b , m . c , m . d , m . e , m . f ] . join ( "," ) + ")" ;
break ;
case 2 : // TRANSLATE
text = "translate(" + m . e + "," + m . f + ")" ;
break ;
case 3 : // SCALE
if ( m . a == m . d ) text = "scale(" + m . a + ")" ;
else text = "scale(" + m . a + "," + m . d + ")" ;
break ;
case 4 : // ROTATE
var cx = 0 , cy = 0 ;
// this prevents divide by zero
if ( xform . angle != 0 ) {
var K = 1 - m . a ;
cy = ( K * m . f + m . b * m . e ) / ( K * K + m . b * m . b ) ;
cx = ( m . e - m . b * cy ) / K ;
}
text = "rotate(" + xform . angle + " " + cx + "," + cy + ")" ;
2010-06-22 08:26:15 -05:00
break ;
}
2010-07-20 07:59:47 -05:00
return text ;
2010-06-22 08:26:15 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
this . _elem = elem || null ;
this . _xforms = [ ] ;
// TODO: how do we capture the undo-ability in the changed transform list?
this . _update = function ( ) {
var tstr = "" ;
var concatMatrix = svgroot . createSVGMatrix ( ) ;
for ( var i = 0 ; i < this . numberOfItems ; ++ i ) {
var xform = this . _list . getItem ( i ) ;
tstr += transformToString ( xform ) + " " ;
}
this . _elem . setAttribute ( "transform" , tstr ) ;
2010-06-22 08:26:15 -05:00
} ;
2010-07-20 07:59:47 -05:00
this . _list = this ;
this . _init = function ( ) {
// Transform attribute parser
var str = this . _elem . getAttribute ( "transform" ) ;
if ( ! str ) return ;
// TODO: Add skew support in future
var re = /\s*((scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/ ;
var arr = [ ] ;
var m = true ;
while ( m ) {
m = str . match ( re ) ;
str = str . replace ( re , '' ) ;
if ( m && m [ 1 ] ) {
var x = m [ 1 ] ;
var bits = x . split ( /\s*\(/ ) ;
var name = bits [ 0 ] ;
var val _bits = bits [ 1 ] . match ( /\s*(.*?)\s*\)/ ) ;
var val _arr = val _bits [ 1 ] . split ( /[, ]+/ ) ;
var letters = 'abcdef' . split ( '' ) ;
var mtx = svgroot . createSVGMatrix ( ) ;
$ . each ( val _arr , function ( i , item ) {
val _arr [ i ] = parseFloat ( item ) ;
if ( name == 'matrix' ) {
mtx [ letters [ i ] ] = val _arr [ i ] ;
}
} ) ;
var xform = svgroot . createSVGTransform ( ) ;
var fname = 'set' + name . charAt ( 0 ) . toUpperCase ( ) + name . slice ( 1 ) ;
var values = name == 'matrix' ? [ mtx ] : val _arr ;
if ( name == 'scale' && values . length == 1 ) {
values . push ( values [ 0 ] ) ;
} else if ( name == 'translate' && values . length == 1 ) {
values . push ( 0 ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
xform [ fname ] . apply ( xform , values ) ;
this . _list . appendItem ( xform ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
this . numberOfItems = 0 ;
this . clear = function ( ) {
2010-02-05 21:36:35 -06:00
this . numberOfItems = 0 ;
2010-07-20 07:59:47 -05:00
this . _xforms = [ ] ;
} ;
this . initialize = function ( newItem ) {
this . numberOfItems = 1 ;
this . _xforms = [ newItem ] ;
} ;
this . getItem = function ( index ) {
if ( index < this . numberOfItems && index >= 0 ) {
return this . _xforms [ index ] ;
}
return null ;
} ;
this . insertItemBefore = function ( newItem , index ) {
var retValue = null ;
if ( index >= 0 ) {
if ( index < this . numberOfItems ) {
var newxforms = new Array ( this . numberOfItems + 1 ) ;
// TODO: use array copying and slicing
for ( var i = 0 ; i < index ; ++ i ) {
2010-02-05 21:36:35 -06:00
newxforms [ i ] = this . _xforms [ i ] ;
}
2010-07-20 07:59:47 -05:00
newxforms [ i ] = newItem ;
for ( var j = i + 1 ; i < this . numberOfItems ; ++ j , ++ i ) {
newxforms [ j ] = this . _xforms [ i ] ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
this . numberOfItems ++ ;
2010-02-05 21:36:35 -06:00
this . _xforms = newxforms ;
2010-07-20 07:59:47 -05:00
retValue = newItem ;
2010-02-05 21:36:35 -06:00
this . _list . _update ( ) ;
}
2010-07-20 07:59:47 -05:00
else {
retValue = this . _list . appendItem ( newItem ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
return retValue ;
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
this . replaceItem = function ( newItem , index ) {
var retValue = null ;
if ( index < this . numberOfItems && index >= 0 ) {
this . _xforms [ index ] = newItem ;
retValue = newItem ;
this . _list . _update ( ) ;
2010-04-01 23:56:21 -05:00
}
2010-07-20 07:59:47 -05:00
return retValue ;
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
this . removeItem = function ( index ) {
var retValue = null ;
if ( index < this . numberOfItems && index >= 0 ) {
var retValue = this . _xforms [ index ] ;
var newxforms = new Array ( this . numberOfItems - 1 ) ;
for ( var i = 0 ; i < index ; ++ i ) {
newxforms [ i ] = this . _xforms [ i ] ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
for ( var j = i ; j < this . numberOfItems - 1 ; ++ j , ++ i ) {
newxforms [ j ] = this . _xforms [ i + 1 ] ;
}
this . numberOfItems -- ;
this . _xforms = newxforms ;
this . _list . _update ( ) ;
}
return retValue ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
this . appendItem = function ( newItem ) {
this . _xforms . push ( newItem ) ;
this . numberOfItems ++ ;
this . _list . _update ( ) ;
return newItem ;
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
} ;
// **************************************************************************************
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Group: Helper functions
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: walkTree
// Walks the tree and executes the callback on each element in a top-down fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
function walkTree ( elem , cbFn ) {
if ( elem && elem . nodeType == 1 ) {
cbFn ( elem ) ;
var i = elem . childNodes . length ;
while ( i -- ) {
walkTree ( elem . childNodes . item ( i ) , cbFn ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: walkTreePost
// Walks the tree and executes the callback on each element in a depth-first fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
function walkTreePost ( elem , cbFn ) {
if ( elem && elem . nodeType == 1 ) {
var i = elem . childNodes . length ;
while ( i -- ) {
walkTree ( elem . childNodes . item ( i ) , cbFn ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
cbFn ( elem ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: assignAttributes
// Assigns multiple attributes to an element.
//
// Parameters:
// node - DOM element to apply new attribute values to
// attrs - Object with attribute keys/values
// suspendLength - Optional integer of milliseconds to suspend redraw
// unitCheck - Boolean to indicate the need to use setUnitAttr
var assignAttributes = this . assignAttributes = function ( node , attrs , suspendLength , unitCheck ) {
if ( ! suspendLength ) suspendLength = 0 ;
// Opera has a problem with suspendRedraw() apparently
var handle = null ;
if ( ! window . opera ) svgroot . suspendRedraw ( suspendLength ) ;
for ( var i in attrs ) {
var ns = ( i . substr ( 0 , 4 ) == "xml:" ? xmlns :
i . substr ( 0 , 6 ) == "xlink:" ? xlinkns : null ) ;
if ( ns || ! unitCheck ) {
node . setAttributeNS ( ns , i , attrs [ i ] ) ;
2010-02-27 09:49:02 -06:00
} else {
2010-07-20 07:59:47 -05:00
setUnitAttr ( node , i , attrs [ i ] ) ;
2010-02-27 09:49:02 -06:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
}
if ( ! window . opera ) svgroot . unsuspendRedraw ( handle ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: cleanupElement
// Remove unneeded (default) attributes, makes resulting SVG smaller
//
// Parameters:
// element - DOM element to clean up
var cleanupElement = this . cleanupElement = function ( element ) {
var handle = svgroot . suspendRedraw ( 60 ) ;
var defaults = {
'fill-opacity' : 1 ,
'stop-opacity' : 1 ,
'opacity' : 1 ,
'stroke' : 'none' ,
'stroke-dasharray' : 'none' ,
'stroke-linejoin' : 'miter' ,
'stroke-linecap' : 'butt' ,
'stroke-opacity' : 1 ,
'stroke-width' : 1 ,
'rx' : 0 ,
'ry' : 0
}
for ( var attr in defaults ) {
var val = defaults [ attr ] ;
if ( element . getAttribute ( attr ) == val ) {
element . removeAttribute ( attr ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
svgroot . unsuspendRedraw ( handle ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: addSvgElementFromJson
// Create a new SVG element based on the given object keys/values and add it to the current layer
// The element will be ran through cleanupElement before being returned
//
// Parameters:
// data - Object with the following keys/values:
// * element - DOM element to create
// * attr - Object with attributes/values to assign to the new element
// * curStyles - Boolean indicating that current style attributes should be applied first
//
// Returns: The new element
var addSvgElementFromJson = this . addSvgElementFromJson = function ( data ) {
var shape = getElem ( data . attr . id ) ;
// if shape is a path but we need to create a rect/ellipse, then remove the path
if ( shape && data . element != shape . tagName ) {
current _layer . removeChild ( shape ) ;
shape = null ;
}
if ( ! shape ) {
shape = svgdoc . createElementNS ( svgns , data . element ) ;
if ( current _layer ) {
current _layer . appendChild ( shape ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
if ( data . curStyles ) {
assignAttributes ( shape , {
"fill" : cur _shape . fill ,
"stroke" : cur _shape . stroke ,
"stroke-width" : cur _shape . stroke _width ,
"stroke-dasharray" : cur _shape . stroke _dasharray ,
"stroke-linejoin" : cur _shape . stroke _linejoin ,
"stroke-linecap" : cur _shape . stroke _linecap ,
"stroke-opacity" : cur _shape . stroke _opacity ,
"fill-opacity" : cur _shape . fill _opacity ,
"opacity" : cur _shape . opacity / 2 ,
"style" : "pointer-events:inherit"
} , 100 ) ;
}
assignAttributes ( shape , data . attr , 100 ) ;
cleanupElement ( shape ) ;
return shape ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
( function ( ) {
// TODO: make this string optional and set by the client
var comment = svgdoc . createComment ( " Created with SVG-edit - http://svg-edit.googlecode.com/ " ) ;
2010-09-10 15:19:23 -05:00
// Lead to invalid content with Instiki's Sanitizer
// svgcontent.appendChild(comment);
2010-07-20 07:59:47 -05:00
// TODO For Issue 208: this is a start on a thumbnail
// var svgthumb = svgdoc.createElementNS(svgns, "use");
// svgthumb.setAttribute('width', '100');
// svgthumb.setAttribute('height', '100');
2010-09-10 15:19:23 -05:00
// setHref(svgthumb, '#svgcontent');
2010-07-20 07:59:47 -05:00
// svgroot.appendChild(svgthumb);
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
} ) ( ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// z-ordered array of tuples containing layer names and <g> elements
// the first layer is the one at the bottom of the rendering
var all _layers = [ ] ,
// Object to contain image data for raster images that were found encodable
encodableImages = { } ,
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// String with image URL of last loadable image
last _good _img _url = curConfig . imgPath + 'logo.png' ,
// pointer to the current layer <g>
current _layer = null ,
// Object with save options
save _options = { round _digits : 5 } ,
// Boolean indicating whether or not a draw action has been started
started = false ,
// Integer with internal ID number for the latest element
obj _num = 1 ,
// String with an element's initial transform attribute value
start _transform = null ,
// String indicating the current editor mode
current _mode = "select" ,
// String with the current direction in which an element is being resized
current _resize _mode = "none" ,
// Object containing data for the currently selected styles
all _properties = {
shape : {
fill : "#" + curConfig . initFill . color ,
fill _paint : null ,
fill _opacity : curConfig . initFill . opacity ,
stroke : "#" + curConfig . initStroke . color ,
stroke _paint : null ,
stroke _opacity : curConfig . initStroke . opacity ,
stroke _width : curConfig . initStroke . width ,
stroke _dasharray : 'none' ,
stroke _linejoin : 'miter' ,
stroke _linecap : 'butt' ,
opacity : curConfig . initOpacity
2010-02-05 21:36:35 -06:00
}
} ;
2010-07-20 07:59:47 -05:00
all _properties . text = $ . extend ( true , { } , all _properties . shape ) ;
$ . extend ( all _properties . text , {
fill : "#000000" ,
stroke _width : 0 ,
font _size : 24 ,
font _family : 'serif'
} ) ;
// Current shape style properties
var cur _shape = all _properties . shape ,
// Current text style properties
cur _text = all _properties . text ,
// Current general properties
cur _properties = cur _shape ,
// Float displaying the current zoom level (1 = 100%, .5 = 50%, etc)
current _zoom = 1 ,
// Array with all the currently selected elements
// default size of 1 until it needs to grow bigger
selectedElements = new Array ( 1 ) ,
// Array with selected elements' Bounding box object
selectedBBoxes = new Array ( 1 ) ,
// The DOM element that was just selected
justSelected = null ,
// this object manages selectors for us
selectorManager = this . selectorManager = new SelectorManager ( ) ,
// DOM element for selection rectangle drawn by the user
rubberBox = null ,
// Array of current BBoxes (still needed?)
curBBoxes = [ ] ,
// Object to contain all included extensions
2010-09-10 15:19:23 -05:00
extensions = { } ,
// Canvas point for the most recent right click
lastClickPoint = null ;
// Clipboard for cut, copy&pasted elements
canvas . clipBoard = [ ] ;
2010-07-20 07:59:47 -05:00
// Should this return an array by default, so extension results aren't overwritten?
var runExtensions = this . runExtensions = function ( action , vars , returnArray ) {
var result = false ;
if ( returnArray ) result = [ ] ;
$ . each ( extensions , function ( name , opts ) {
if ( action in opts ) {
if ( returnArray ) {
result . push ( opts [ action ] ( vars ) )
} else {
result = opts [ action ] ( vars ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
} ) ;
return result ;
}
// Function: addExtension
// Add an extension to the editor
//
// Parameters:
// name - String with the ID of the extension
// ext_func - Function supplied by the extension with its data
this . addExtension = function ( name , ext _func ) {
if ( ! ( name in extensions ) ) {
// Provide private vars/funcs here. Is there a better way to do this?
if ( $ . isFunction ( ext _func ) ) {
var ext = ext _func ( $ . extend ( canvas . getPrivateMethods ( ) , {
svgroot : svgroot ,
svgcontent : svgcontent ,
nonce : nonce ,
selectorManager : selectorManager
} ) ) ;
} else {
var ext = ext _func ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
extensions [ name ] = ext ;
call ( "extension_added" , ext ) ;
} else {
console . log ( 'Cannot add extension "' + name + '", an extension by that name already exists"' ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: shortFloat
// Rounds a given value to a float with number of digits defined in save_options
//
// Parameters:
// val - The value as a String, Number or Array of two numbers to be rounded
//
// Returns:
// If a string/number was given, returns a Float. If an array, return a string
// with comma-seperated floats
var shortFloat = function ( val ) {
var digits = save _options . round _digits ;
if ( ! isNaN ( val ) ) {
return Number ( Number ( val ) . toFixed ( digits ) ) ;
} else if ( $ . isArray ( val ) ) {
return shortFloat ( val [ 0 ] ) + ',' + shortFloat ( val [ 1 ] ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
// This method rounds the incoming value to the nearest value based on the current_zoom
var round = this . round = function ( val ) {
return parseInt ( val * current _zoom ) / current _zoom ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// This method sends back an array or a NodeList full of elements that
// intersect the multi-select rubber-band-box on the current_layer only.
//
// Since the only browser that supports the SVG DOM getIntersectionList is Opera,
// we need to provide an implementation here. We brute-force it for now.
//
// Reference:
// Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421
// Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274
var getIntersectionList = this . getIntersectionList = function ( rect ) {
if ( rubberBox == null ) { return null ; }
if ( ! curBBoxes . length ) {
// Cache all bboxes
curBBoxes = getVisibleElements ( current _layer , true ) ;
}
var resultList = null ;
try {
resultList = current _layer . getIntersectionList ( rect , null ) ;
} catch ( e ) { }
2010-02-12 00:01:27 -06:00
2010-07-20 07:59:47 -05:00
if ( resultList == null || typeof ( resultList . item ) != "function" ) {
resultList = [ ] ;
2010-09-10 15:19:23 -05:00
if ( ! rect ) {
var rubberBBox = rubberBox . getBBox ( ) ;
var bb = { } ;
$ . each ( rubberBBox , function ( key , val ) {
// Can't set values to a real BBox object, so make a fake one
bb [ key ] = val / current _zoom ;
} ) ;
rubberBBox = bb ;
} else {
var rubberBBox = rect ;
}
2010-07-20 07:59:47 -05:00
var i = curBBoxes . length ;
while ( i -- ) {
if ( ! rubberBBox . width || ! rubberBBox . width ) continue ;
if ( Utils . rectsIntersect ( rubberBBox , curBBoxes [ i ] . bbox ) ) {
resultList . push ( curBBoxes [ i ] . elem ) ;
}
}
}
// addToSelection expects an array, but it's ok to pass a NodeList
// because using square-bracket notation is allowed:
// http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
return resultList ;
} ;
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
// Function: getStrokedBBox
// Get the bounding box for one or more stroked and/or transformed elements
//
// Parameters:
// elems - Array with DOM elements to check
//
// Returns:
// A single bounding box object
var getStrokedBBox = this . getStrokedBBox = function ( elems ) {
if ( ! elems ) elems = getVisibleElements ( ) ;
if ( ! elems . length ) return false ;
// Make sure the expected BBox is returned if the element is a group
var getCheckedBBox = function ( elem ) {
try {
// TODO: Fix issue with rotated groups. Currently they work
// fine in FF, but not in other browsers (same problem mentioned
// in Issue 339 comment #2).
var bb = getBBox ( elem ) ;
var angle = getRotationAngle ( elem ) ;
if ( ( angle && angle % 90 ) || hasMatrixTransform ( getTransformList ( elem ) ) ) {
// Accurate way to get BBox of rotated element in Firefox:
// Put element in group and get its BBox
var good _bb = false ;
// Get the BBox from the raw path for these elements
var elemNames = [ 'ellipse' , 'path' , 'line' , 'polyline' , 'polygon' ] ;
if ( $ . inArray ( elem . tagName , elemNames ) != - 1 ) {
bb = good _bb = canvas . convertToPath ( elem , true ) ;
} else if ( elem . tagName == 'rect' ) {
// Look for radius
var rx = elem . getAttribute ( 'rx' ) ;
var ry = elem . getAttribute ( 'ry' ) ;
if ( rx || ry ) {
bb = good _bb = canvas . convertToPath ( elem , true ) ;
2010-03-18 10:32:47 -05:00
}
}
2010-07-20 07:59:47 -05:00
if ( ! good _bb ) {
var g = document . createElementNS ( svgns , "g" ) ;
var parent = elem . parentNode ;
parent . replaceChild ( g , elem ) ;
g . appendChild ( elem ) ;
bb = g . getBBox ( ) ;
parent . insertBefore ( elem , g ) ;
parent . removeChild ( g ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Old method: Works by giving the rotated BBox,
// this is (unfortunately) what Opera and Safari do
// natively when getting the BBox of the parent group
// var angle = angle * Math.PI / 180.0;
// var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE,
// rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE;
// var cx = round(bb.x + bb.width/2),
// cy = round(bb.y + bb.height/2);
// var pts = [ [bb.x - cx, bb.y - cy],
// [bb.x + bb.width - cx, bb.y - cy],
// [bb.x + bb.width - cx, bb.y + bb.height - cy],
// [bb.x - cx, bb.y + bb.height - cy] ];
// var j = 4;
// while (j--) {
// var x = pts[j][0],
// y = pts[j][1],
// r = Math.sqrt( x*x + y*y );
// var theta = Math.atan2(y,x) + angle;
// x = round(r * Math.cos(theta) + cx);
// y = round(r * Math.sin(theta) + cy);
//
// // now set the bbox for the shape after it's been rotated
// if (x < rminx) rminx = x;
// if (y < rminy) rminy = y;
// if (x > rmaxx) rmaxx = x;
// if (y > rmaxy) rmaxy = y;
// }
//
// bb.x = rminx;
// bb.y = rminy;
// bb.width = rmaxx - rminx;
// bb.height = rmaxy - rminy;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return bb ;
} catch ( e ) {
console . log ( elem , e ) ;
return null ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
}
var full _bb ;
$ . each ( elems , function ( ) {
if ( full _bb ) return ;
if ( ! this . parentNode ) return ;
full _bb = getCheckedBBox ( this ) ;
2010-09-10 15:19:23 -05:00
if ( full _bb ) {
var b = { } ;
for ( var i in full _bb ) b [ i ] = full _bb [ i ] ;
full _bb = b ;
}
2010-07-20 07:59:47 -05:00
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// This shouldn't ever happen...
if ( full _bb == null ) return null ;
// full_bb doesn't include the stoke, so this does no good!
// if(elems.length == 1) return full_bb;
var max _x = full _bb . x + full _bb . width ;
var max _y = full _bb . y + full _bb . height ;
var min _x = full _bb . x ;
var min _y = full _bb . y ;
// FIXME: same re-creation problem with this function as getCheckedBBox() above
var getOffset = function ( elem ) {
var sw = elem . getAttribute ( "stroke-width" ) ;
var offset = 0 ;
if ( elem . getAttribute ( "stroke" ) != "none" && ! isNaN ( sw ) ) {
offset += sw / 2 ;
}
return offset ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var bboxes = [ ] ;
$ . each ( elems , function ( i , elem ) {
var cur _bb = getCheckedBBox ( elem ) ;
if ( cur _bb ) {
var offset = getOffset ( elem ) ;
min _x = Math . min ( min _x , cur _bb . x - offset ) ;
min _y = Math . min ( min _y , cur _bb . y - offset ) ;
bboxes . push ( cur _bb ) ;
}
} ) ;
full _bb . x = min _x ;
full _bb . y = min _y ;
$ . each ( elems , function ( i , elem ) {
var cur _bb = bboxes [ i ] ;
// ensure that elem is really an element node
if ( cur _bb && elem . nodeType == 1 ) {
var offset = getOffset ( elem ) ;
max _x = Math . max ( max _x , cur _bb . x + cur _bb . width + offset ) ;
max _y = Math . max ( max _y , cur _bb . y + cur _bb . height + offset ) ;
}
} ) ;
full _bb . width = max _x - min _x ;
full _bb . height = max _y - min _y ;
return full _bb ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getVisibleElements
// Get all elements that have a BBox (excludes <defs>, <title>, etc).
// Note that 0-opacity, off-screen etc elements are still considered "visible"
// for this function
//
// Parameters:
// parent - The parent DOM element to search within
// includeBBox - Boolean to indicate that an object should return with the element and its bbox
//
// Returns:
// An array with all "visible" elements, or if includeBBox is true, an array with
// objects that include:
// * elem - The element
// * bbox - The element's BBox as retrieved from getStrokedBBox
var getVisibleElements = this . getVisibleElements = function ( parent , includeBBox ) {
if ( ! parent ) parent = $ ( svgcontent ) . children ( ) ; // Prevent layers from being included
var contentElems = [ ] ;
$ ( parent ) . children ( ) . each ( function ( i , elem ) {
try {
var box = elem . getBBox ( ) ;
if ( box ) {
var item = includeBBox ? { 'elem' : elem , 'bbox' : getStrokedBBox ( [ elem ] ) } : elem ;
contentElems . push ( item ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} catch ( e ) { }
} ) ;
return contentElems . reverse ( ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: groupSvgElem
// Wrap an SVG element into a group element, mark the group as 'gsvg'
//
// Parameters:
// elem - SVG element to wrap
var groupSvgElem = this . groupSvgElem = function ( elem ) {
var g = document . createElementNS ( svgns , "g" ) ;
elem . parentNode . replaceChild ( g , elem ) ;
$ ( g ) . append ( elem ) . data ( 'gsvg' , elem ) [ 0 ] . id = getNextId ( ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: copyElem
// Create a clone of an element, updating its ID and its children's IDs when needed
//
// Parameters:
// el - DOM element to clone
//
// Returns: The cloned element
var copyElem = function ( el ) {
// manually create a copy of the element
var new _el = document . createElementNS ( el . namespaceURI , el . nodeName ) ;
$ . each ( el . attributes , function ( i , attr ) {
if ( attr . localName != '-moz-math-font-style' ) {
new _el . setAttributeNS ( attr . namespaceURI , attr . nodeName , attr . nodeValue ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
// set the copied element's new id
new _el . removeAttribute ( "id" ) ;
new _el . id = getNextId ( ) ;
// manually increment obj_num because our cloned elements are not in the DOM yet
obj _num ++ ;
// Opera's "d" value needs to be reset for Opera/Win/non-EN
// Also needed for webkit (else does not keep curved segments on clone)
if ( ( isWebkit ) && el . nodeName == 'path' ) {
var fixed _d = pathActions . convertPath ( el ) ;
new _el . setAttribute ( 'd' , fixed _d ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// now create copies of all children
$ . each ( el . childNodes , function ( i , child ) {
switch ( child . nodeType ) {
case 1 : // element node
new _el . appendChild ( copyElem ( child ) ) ;
break ;
case 3 : // text node
new _el . textContent = child . nodeValue ;
break ;
default :
break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
if ( $ ( el ) . data ( 'gsvg' ) ) {
$ ( new _el ) . data ( 'gsvg' , new _el . firstChild ) ;
2010-08-13 16:14:19 -05:00
} else if ( $ ( el ) . data ( 'symbol' ) ) {
var ref = $ ( el ) . data ( 'symbol' ) ;
$ ( new _el ) . data ( 'ref' , ref ) . data ( 'symbol' , ref ) ;
}
else if ( new _el . tagName == 'image' ) {
2010-07-20 07:59:47 -05:00
preventClickDefault ( new _el ) ;
}
return new _el ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getElem
// Get a DOM element by ID within the SVG root element.
//
// Parameters:
// id - String with the element's new ID
function getElem ( id ) {
// if(svgroot.getElementById) {
// // getElementById lookup
// return svgroot.getElementById(id);
// } else
if ( svgroot . querySelector ) {
// querySelector lookup
return svgroot . querySelector ( '#' + id ) ;
} else if ( svgdoc . evaluate ) {
// xpath lookup
return svgdoc . evaluate ( 'svg:svg[@id="svgroot"]//svg:*[@id="' + id + '"]' , container , function ( ) { return "http://www.w3.org/2000/svg" ; } , 9 , null ) . singleNodeValue ;
} else {
// jQuery lookup: twice as slow as xpath in FF
return $ ( svgroot ) . find ( '[id=' + id + ']' ) [ 0 ] ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// getElementById lookup: includes icons, not good
// return svgdoc.getElementById(id);
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Set scope for these functions
var getId , getNextId ;
( function ( c ) {
// Object to contain editor event names and callback functions
var events = { } ;
// Prefix string for element IDs
var idprefix = "svg_" ;
// Function: getId
// Returns the last created DOM element ID string
getId = c . getId = function ( ) {
if ( events [ "getid" ] ) return call ( "getid" , obj _num ) ;
if ( randomize _ids ) {
return idprefix + nonce + '_' + obj _num ;
} else {
return idprefix + obj _num ;
2010-02-05 21:36:35 -06:00
}
} ;
2010-07-20 07:59:47 -05:00
// Function: getNextId
// Creates and returns a unique ID string for a DOM element
getNextId = c . getNextId = function ( ) {
// ensure the ID does not exist
var id = getId ( ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
while ( getElem ( id ) ) {
obj _num ++ ;
id = getId ( ) ;
}
return id ;
} ;
// Run the callback function associated with the given event
//
// Parameters:
// event - String with the event name
// arg - Argument to pass through to the callback function
call = c . call = function ( event , arg ) {
if ( events [ event ] ) {
return events [ event ] ( this , arg ) ;
}
} ;
// Function: bind
// Attaches a callback function to an event
//
// Parameters:
// event - String indicating the name of the event
// f - The callback function to bind to the event
//
// Return:
// The previous event
c . bind = function ( event , f ) {
var old = events [ event ] ;
events [ event ] = f ;
return old ;
} ;
// Function: setIdPrefix
// Changes the ID prefix to the given value
//
// Parameters:
// p - String with the new prefix
c . setIdPrefix = function ( p ) {
idprefix = p ;
} ;
} ( canvas ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: sanitizeSvg
// Sanitizes the input node and its children
// It only keeps what is allowed from our whitelist defined above
//
// Parameters:
// node - The DOM element to be checked, will also check its children
var sanitizeSvg = this . sanitizeSvg = function ( node ) {
// we only care about element nodes
// automatically return for all comment, etc nodes
// for text, we do a whitespace trim
if ( node . nodeType == 3 ) {
node . nodeValue = node . nodeValue . replace ( /^\s+|\s+$/g , "" ) ;
// Remove empty text nodes
if ( ! node . nodeValue . length ) node . parentNode . removeChild ( node ) ;
}
if ( node . nodeType != 1 ) return ;
var doc = node . ownerDocument ;
var parent = node . parentNode ;
// can parent ever be null here? I think the root node's parent is the document...
if ( ! doc || ! parent ) return ;
var allowedAttrs = svgWhiteList [ node . nodeName ] ;
var allowedAttrsNS = svgWhiteListNS [ node . nodeName ] ;
// if this element is allowed
if ( allowedAttrs != undefined ) {
var se _attrs = [ ] ;
var i = node . attributes . length ;
while ( i -- ) {
// if the attribute is not in our whitelist, then remove it
// could use jQuery's inArray(), but I don't know if that's any better
var attr = node . attributes . item ( i ) ;
var attrName = attr . nodeName ;
var attrLocalName = attr . localName ;
var attrNsURI = attr . namespaceURI ;
// Check that an attribute with the correct localName in the correct namespace is on
// our whitelist or is a namespace declaration for one of our allowed namespaces
if ( ! ( allowedAttrsNS . hasOwnProperty ( attrLocalName ) && attrNsURI == allowedAttrsNS [ attrLocalName ] && attrNsURI != xmlnsns ) &&
! ( attrNsURI == xmlnsns && nsMap [ attr . nodeValue ] ) )
{
// Bypassing the whitelist to allow se: prefixes. Is there
// a more appropriate way to do this?
if ( attrName . indexOf ( 'se:' ) == 0 ) {
se _attrs . push ( [ attrName , attr . nodeValue ] ) ;
}
node . removeAttributeNS ( attrNsURI , attrLocalName ) ;
}
// special handling for path d attribute
if ( node . nodeName == 'path' && attrName == 'd' ) {
// Convert to absolute
node . setAttribute ( 'd' , pathActions . convertPath ( node ) ) ;
pathActions . fixEnd ( node ) ;
}
// for the style attribute, rewrite it in terms of XML presentational attributes
if ( attrName == "style" ) {
var props = attr . nodeValue . split ( ";" ) ,
p = props . length ;
while ( p -- ) {
var nv = props [ p ] . split ( ":" ) ;
// now check that this attribute is supported
if ( allowedAttrs . indexOf ( nv [ 0 ] ) != - 1 ) {
node . setAttribute ( nv [ 0 ] , nv [ 1 ] ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
node . removeAttribute ( 'style' ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
$ . each ( se _attrs , function ( i , attr ) {
node . setAttributeNS ( se _ns , attr [ 0 ] , attr [ 1 ] ) ;
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// for some elements that have a xlink:href, ensure the URI refers to a local element
// (but not for links)
2010-09-10 15:19:23 -05:00
var href = getHref ( node ) ;
2010-07-20 07:59:47 -05:00
if ( href &&
$ . inArray ( node . nodeName , [ "filter" , "linearGradient" , "pattern" ,
"radialGradient" , "textPath" , "use" ] ) != - 1 )
2010-02-05 21:36:35 -06:00
{
2010-07-20 07:59:47 -05:00
// TODO: we simply check if the first character is a #, is this bullet-proof?
if ( href [ 0 ] != "#" ) {
// remove the attribute (but keep the element)
2010-09-10 15:19:23 -05:00
setHref ( node , "" ) ;
2010-07-20 07:59:47 -05:00
node . removeAttributeNS ( xlinkns , "href" ) ;
}
2010-02-09 11:44:43 -06:00
}
2010-07-20 07:59:47 -05:00
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
2010-09-10 15:19:23 -05:00
if ( node . nodeName == "use" && ! getHref ( node ) ) {
2010-07-20 07:59:47 -05:00
parent . removeChild ( node ) ;
return ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// if the element has attributes pointing to a non-local reference,
// need to remove the attribute
$ . each ( [ "clip-path" , "fill" , "filter" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "stroke" ] , function ( i , attr ) {
var val = node . getAttribute ( attr ) ;
if ( val ) {
val = getUrlFromAttr ( val ) ;
// simply check for first character being a '#'
if ( val && val [ 0 ] != "#" ) {
node . setAttribute ( attr , "" ) ;
node . removeAttribute ( attr ) ;
}
}
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// recurse to children
i = node . childNodes . length ;
while ( i -- ) { sanitizeSvg ( node . childNodes . item ( i ) ) ; }
}
// else, remove this element
else {
// remove all children from this node and insert them before this node
// FIXME: in the case of animation elements this will hardly ever be correct
var children = [ ] ;
while ( node . hasChildNodes ( ) ) {
children . push ( parent . insertBefore ( node . firstChild , node ) ) ;
}
// remove this node from the document altogether
parent . removeChild ( node ) ;
// call sanitizeSvg on each of those children
var i = children . length ;
while ( i -- ) { sanitizeSvg ( children [ i ] ) ; }
}
} ;
// Function: getUrlFromAttr
// Extracts the URL from the url(...) syntax of some attributes.
// Three variants:
// * <circle fill="url(someFile.svg#foo)" />
// * <circle fill="url('someFile.svg#foo')" />
// * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
//
// Returns:
// String with just the URL, like someFile.svg#foo
var getUrlFromAttr = this . getUrlFromAttr = function ( attrVal ) {
if ( attrVal ) {
// url("#somegrad")
if ( attrVal . indexOf ( 'url("' ) == 0 ) {
return attrVal . substring ( 5 , attrVal . indexOf ( '"' , 6 ) ) ;
}
// url('#somegrad')
else if ( attrVal . indexOf ( "url('" ) == 0 ) {
return attrVal . substring ( 5 , attrVal . indexOf ( "'" , 6 ) ) ;
}
else if ( attrVal . indexOf ( "url(" ) == 0 ) {
return attrVal . substring ( 4 , attrVal . indexOf ( ')' ) ) ;
}
}
return null ;
} ;
// Function: getBBox
// Get the given/selected element's bounding box object, convert it to be more
// usable when necessary
//
// Parameters:
// elem - Optional DOM element to get the BBox for
var getBBox = this . getBBox = function ( elem ) {
var selected = elem || selectedElements [ 0 ] ;
if ( elem . nodeType != 1 ) return null ;
var ret = null ;
if ( elem . nodeName == 'text' && selected . textContent == '' ) {
selected . textContent = 'a' ; // Some character needed for the selector to use.
ret = selected . getBBox ( ) ;
selected . textContent = '' ;
} else if ( elem . nodeName == 'path' && isWebkit ) {
ret = getPathBBox ( selected ) ;
} else if ( elem . nodeName == 'use' && ! isWebkit ) {
ret = selected . getBBox ( ) ;
ret . x += parseFloat ( selected . getAttribute ( 'x' ) || 0 ) ;
ret . y += parseFloat ( selected . getAttribute ( 'y' ) || 0 ) ;
} else if ( elem . nodeName == 'foreignObject' ) {
ret = selected . getBBox ( ) ;
ret . x += parseFloat ( selected . getAttribute ( 'x' ) || 0 ) ;
ret . y += parseFloat ( selected . getAttribute ( 'y' ) || 0 ) ;
} else {
try { ret = selected . getBBox ( ) ; }
catch ( e ) {
// Check if element is child of a foreignObject
var fo = $ ( selected ) . closest ( "foreignObject" ) ;
if ( fo . length ) {
try {
ret = fo [ 0 ] . getBBox ( ) ;
} catch ( e ) {
ret = null ;
2010-02-10 15:41:47 -06:00
}
2010-07-20 07:59:47 -05:00
} else {
ret = null ;
}
}
}
// get the bounding box from the DOM (which is in that element's coordinate system)
return ret ;
} ;
// Function: ffClone
// Hack for Firefox bugs where text element features aren't updated.
// This function clones the element and re-selects it
// TODO: Test for this bug on load and add it to "support" object instead of
// browser sniffing
//
// Parameters:
// elem - The (text) DOM element to clone
var ffClone = function ( elem ) {
if ( navigator . userAgent . indexOf ( 'Gecko/' ) == - 1 ) return elem ;
var clone = elem . cloneNode ( true )
elem . parentNode . insertBefore ( clone , elem ) ;
elem . parentNode . removeChild ( elem ) ;
selectorManager . releaseSelector ( elem ) ;
selectedElements [ 0 ] = clone ;
selectorManager . requestSelector ( clone ) . showGrips ( true ) ;
return clone ;
}
// Function: getPathBBox
// Get correct BBox for a path in Webkit
// Converted from code found here:
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
//
// Parameters:
// path - The path DOM element to get the BBox for
//
// Returns:
// A BBox-like object
var getPathBBox = function ( path ) {
var seglist = path . pathSegList ;
var tot = seglist . numberOfItems ;
var bounds = [ [ ] , [ ] ] ;
var start = seglist . getItem ( 0 ) ;
var P0 = [ start . x , start . y ] ;
for ( var i = 0 ; i < tot ; i ++ ) {
var seg = seglist . getItem ( i ) ;
if ( ! seg . x ) continue ;
// Add actual points to limits
bounds [ 0 ] . push ( P0 [ 0 ] ) ;
bounds [ 1 ] . push ( P0 [ 1 ] ) ;
if ( seg . x1 ) {
var P1 = [ seg . x1 , seg . y1 ] ,
P2 = [ seg . x2 , seg . y2 ] ,
P3 = [ seg . x , seg . y ] ;
for ( var j = 0 ; j < 2 ; j ++ ) {
var calc = function ( t ) {
return Math . pow ( 1 - t , 3 ) * P0 [ j ]
+ 3 * Math . pow ( 1 - t , 2 ) * t * P1 [ j ]
+ 3 * ( 1 - t ) * Math . pow ( t , 2 ) * P2 [ j ]
+ Math . pow ( t , 3 ) * P3 [ j ] ;
} ;
var b = 6 * P0 [ j ] - 12 * P1 [ j ] + 6 * P2 [ j ] ;
var a = - 3 * P0 [ j ] + 9 * P1 [ j ] - 9 * P2 [ j ] + 3 * P3 [ j ] ;
var c = 3 * P1 [ j ] - 3 * P0 [ j ] ;
if ( a == 0 ) {
if ( b == 0 ) {
continue ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var t = - c / b ;
if ( 0 < t && t < 1 ) {
bounds [ j ] . push ( calc ( t ) ) ;
}
continue ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var b2ac = Math . pow ( b , 2 ) - 4 * c * a ;
if ( b2ac < 0 ) continue ;
var t1 = ( - b + Math . sqrt ( b2ac ) ) / ( 2 * a ) ;
if ( 0 < t1 && t1 < 1 ) bounds [ j ] . push ( calc ( t1 ) ) ;
var t2 = ( - b - Math . sqrt ( b2ac ) ) / ( 2 * a ) ;
if ( 0 < t2 && t2 < 1 ) bounds [ j ] . push ( calc ( t2 ) ) ;
2010-02-13 00:11:13 -06:00
}
2010-07-20 07:59:47 -05:00
P0 = P3 ;
} else {
bounds [ 0 ] . push ( seg . x ) ;
bounds [ 1 ] . push ( seg . y ) ;
}
}
var x = Math . min . apply ( null , bounds [ 0 ] ) ;
var w = Math . max . apply ( null , bounds [ 0 ] ) - x ;
var y = Math . min . apply ( null , bounds [ 1 ] ) ;
var h = Math . max . apply ( null , bounds [ 1 ] ) - y ;
return {
'x' : x ,
'y' : y ,
'width' : w ,
'height' : h
} ;
}
2010-05-22 14:34:08 -05:00
2010-07-20 07:59:47 -05:00
// this.each is deprecated, if any extension used this it can be recreated by doing this:
// $(canvas.getRootElem()).children().each(...)
// this.each = function(cb) {
// $(svgroot).children().each(cb);
// };
// Group: Element Transforms
// Function: getRotationAngle
// Get the rotation angle of the given/selected DOM element
//
// Parameters:
// elem - Optional DOM element to get the angle for
// to_rad - Boolean that when true returns the value in radians rather than degrees
//
// Returns:
// Float with the angle in degrees or radians
var getRotationAngle = this . getRotationAngle = function ( elem , to _rad ) {
var selected = elem || selectedElements [ 0 ] ;
// find the rotation transform (if any) and set it
var tlist = getTransformList ( selected ) ;
if ( ! tlist ) return 0 ; // <svg> elements have no tlist
var N = tlist . numberOfItems ;
for ( var i = 0 ; i < N ; ++ i ) {
var xform = tlist . getItem ( i ) ;
if ( xform . type == 4 ) {
return to _rad ? xform . angle * Math . PI / 180.0 : xform . angle ;
}
}
return 0.0 ;
} ;
// Function: setRotationAngle
// Removes any old rotations if present, prepends a new rotation at the
// transformed center
//
// Parameters:
// val - The new rotation angle in degrees
// preventUndo - Boolean indicating whether the action should be undoable or not
this . setRotationAngle = function ( val , preventUndo ) {
// ensure val is the proper type
val = parseFloat ( val ) ;
var elem = selectedElements [ 0 ] ;
var oldTransform = elem . getAttribute ( "transform" ) ;
var bbox = getBBox ( elem ) ;
var cx = bbox . x + bbox . width / 2 , cy = bbox . y + bbox . height / 2 ;
var tlist = getTransformList ( elem ) ;
// only remove the real rotational transform if present (i.e. at index=0)
if ( tlist . numberOfItems > 0 ) {
var xform = tlist . getItem ( 0 ) ;
if ( xform . type == 4 ) {
tlist . removeItem ( 0 ) ;
}
}
// find R_nc and insert it
if ( val != 0 ) {
var center = transformPoint ( cx , cy , transformListToTransform ( tlist ) . matrix ) ;
var R _nc = svgroot . createSVGTransform ( ) ;
R _nc . setRotate ( val , center . x , center . y ) ;
2010-09-10 15:19:23 -05:00
if ( tlist . numberOfItems ) {
tlist . insertItemBefore ( R _nc , 0 ) ;
} else {
tlist . appendItem ( R _nc ) ;
}
2010-07-20 07:59:47 -05:00
}
else if ( tlist . numberOfItems == 0 ) {
elem . removeAttribute ( "transform" ) ;
}
if ( ! preventUndo ) {
// we need to undo it, then redo it so it can be undo-able! :)
// TODO: figure out how to make changes to transform list undo-able cross-browser?
var newTransform = elem . getAttribute ( "transform" ) ;
elem . setAttribute ( "transform" , oldTransform ) ;
changeSelectedAttribute ( "transform" , newTransform , selectedElements ) ;
}
var pointGripContainer = getElem ( "pathpointgrip_container" ) ;
// if(elem.nodeName == "path" && pointGripContainer) {
// pathActions.setPointContainerTransform(elem.getAttribute("transform"));
// }
var selector = selectorManager . requestSelector ( selectedElements [ 0 ] ) ;
selector . resize ( ) ;
selector . updateGripCursors ( val ) ;
} ;
2010-05-22 14:34:08 -05:00
2010-07-20 07:59:47 -05:00
// Function: getTransformList
// Returns an object that behaves like a SVGTransformList for the given DOM element
//
// Parameters:
// elem - DOM element to get a transformlist from
var getTransformList = this . getTransformList = function ( elem ) {
if ( isWebkit ) {
var id = elem . id ;
if ( ! id ) {
// Get unique ID for temporary element
id = 'temp' ;
}
var t = svgTransformLists [ id ] ;
if ( ! t || id == 'temp' ) {
svgTransformLists [ id ] = new SVGEditTransformList ( elem ) ;
svgTransformLists [ id ] . _init ( ) ;
t = svgTransformLists [ id ] ;
}
return t ;
}
else if ( elem . transform ) {
return elem . transform . baseVal ;
}
else if ( elem . gradientTransform ) {
return elem . gradientTransform . baseVal ;
}
return null ;
} ;
// Function: recalculateAllSelectedDimensions
// Runs recalculateDimensions on the selected elements,
// adding the changes to a single batch command
var recalculateAllSelectedDimensions = this . recalculateAllSelectedDimensions = function ( ) {
var text = ( current _resize _mode == "none" ? "position" : "size" ) ;
var batchCmd = new BatchCommand ( text ) ;
var i = selectedElements . length ;
while ( i -- ) {
var elem = selectedElements [ i ] ;
// if(getRotationAngle(elem) && !hasMatrixTransform(getTransformList(elem))) continue;
var cmd = recalculateDimensions ( elem ) ;
if ( cmd ) {
batchCmd . addSubCommand ( cmd ) ;
}
}
if ( ! batchCmd . isEmpty ( ) ) {
addCommandToHistory ( batchCmd ) ;
call ( "changed" , selectedElements ) ;
}
} ;
// this is how we map paths to our preferred relative segment types
var pathMap = [ 0 , 'z' , 'M' , 'm' , 'L' , 'l' , 'C' , 'c' , 'Q' , 'q' , 'A' , 'a' ,
'H' , 'h' , 'V' , 'v' , 'S' , 's' , 'T' , 't' ] ;
// Debug tool to easily see the current matrix in the browser's console
var logMatrix = function ( m ) {
console . log ( [ m . a , m . b , m . c , m . d , m . e , m . f ] ) ;
} ;
// Function: remapElement
// Applies coordinate changes to an element based on the given matrix
//
// Parameters:
// selected - DOM element to be changed
// changes - Object with changes to be remapped
// m - Matrix object to use for remapping coordinates
var remapElement = this . remapElement = function ( selected , changes , m ) {
var remap = function ( x , y ) { return transformPoint ( x , y , m ) ; } ,
scalew = function ( w ) { return m . a * w ; } ,
scaleh = function ( h ) { return m . d * h ; } ,
box = getBBox ( selected ) ;
switch ( selected . tagName )
{
case "line" :
var pt1 = remap ( changes [ "x1" ] , changes [ "y1" ] ) ,
pt2 = remap ( changes [ "x2" ] , changes [ "y2" ] ) ;
changes [ "x1" ] = pt1 . x ;
changes [ "y1" ] = pt1 . y ;
changes [ "x2" ] = pt2 . x ;
changes [ "y2" ] = pt2 . y ;
break ;
case "circle" :
var c = remap ( changes [ "cx" ] , changes [ "cy" ] ) ;
changes [ "cx" ] = c . x ;
changes [ "cy" ] = c . y ;
// take the minimum of the new selected box's dimensions for the new circle radius
var tbox = transformBox ( box . x , box . y , box . width , box . height , m ) ;
var w = tbox . tr . x - tbox . tl . x , h = tbox . bl . y - tbox . tl . y ;
changes [ "r" ] = Math . min ( w / 2 , h / 2 ) ;
break ;
case "ellipse" :
var c = remap ( changes [ "cx" ] , changes [ "cy" ] ) ;
changes [ "cx" ] = c . x ;
changes [ "cy" ] = c . y ;
changes [ "rx" ] = scalew ( changes [ "rx" ] ) ;
changes [ "ry" ] = scaleh ( changes [ "ry" ] ) ;
break ;
case "foreignObject" :
case "rect" :
case "image" :
var pt1 = remap ( changes [ "x" ] , changes [ "y" ] ) ;
changes [ "x" ] = pt1 . x ;
changes [ "y" ] = pt1 . y ;
changes [ "width" ] = scalew ( changes [ "width" ] ) ;
changes [ "height" ] = scaleh ( changes [ "height" ] ) ;
break ;
case "use" :
2010-08-13 16:14:19 -05:00
// var pt1 = remap(changes["x"],changes["y"]);
// changes["x"] = pt1.x;
// changes["y"] = pt1.y;
// break;
2010-07-20 07:59:47 -05:00
case "g" :
case "text" :
// if it was a translate, then just update x,y
if ( m . a == 1 && m . b == 0 && m . c == 0 && m . d == 1 &&
( m . e != 0 || m . f != 0 ) )
2010-02-05 21:36:35 -06:00
{
2010-07-20 07:59:47 -05:00
// [T][M] = [M][T']
// therefore [T'] = [M_inv][T][M]
var existing = transformListToTransform ( selected ) . matrix ,
t _new = matrixMultiply ( existing . inverse ( ) , m , existing ) ;
changes [ "x" ] = parseFloat ( changes [ "x" ] ) + t _new . e ;
changes [ "y" ] = parseFloat ( changes [ "y" ] ) + t _new . f ;
}
else {
// we just absorb all matrices into the element and don't do any remapping
var chlist = getTransformList ( selected ) ;
var mt = svgroot . createSVGTransform ( ) ;
mt . setMatrix ( matrixMultiply ( transformListToTransform ( chlist ) . matrix , m ) ) ;
chlist . clear ( ) ;
chlist . appendItem ( mt ) ;
}
break ;
case "polygon" :
case "polyline" :
var len = changes [ "points" ] . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var pt = changes [ "points" ] [ i ] ;
pt = remap ( pt . x , pt . y ) ;
changes [ "points" ] [ i ] . x = pt . x ;
changes [ "points" ] [ i ] . y = pt . y ;
}
break ;
case "path" :
var segList = selected . pathSegList ;
var len = segList . numberOfItems ;
changes . d = new Array ( len ) ;
for ( var i = 0 ; i < len ; ++ i ) {
var seg = segList . getItem ( i ) ;
changes . d [ i ] = {
type : seg . pathSegType ,
x : seg . x ,
y : seg . y ,
x1 : seg . x1 ,
y1 : seg . y1 ,
x2 : seg . x2 ,
y2 : seg . y2 ,
r1 : seg . r1 ,
r2 : seg . r2 ,
angle : seg . angle ,
largeArcFlag : seg . largeArcFlag ,
sweepFlag : seg . sweepFlag
} ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var len = changes [ "d" ] . length ,
firstseg = changes [ "d" ] [ 0 ] ,
currentpt = remap ( firstseg . x , firstseg . y ) ;
changes [ "d" ] [ 0 ] . x = currentpt . x ;
changes [ "d" ] [ 0 ] . y = currentpt . y ;
for ( var i = 1 ; i < len ; ++ i ) {
var seg = changes [ "d" ] [ i ] ;
var type = seg . type ;
// if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
// if relative, we want to scalew, scaleh
if ( type % 2 == 0 ) { // absolute
var thisx = ( seg . x != undefined ) ? seg . x : currentpt . x , // for V commands
thisy = ( seg . y != undefined ) ? seg . y : currentpt . y , // for H commands
pt = remap ( thisx , thisy ) ,
pt1 = remap ( seg . x1 , seg . y1 ) ,
pt2 = remap ( seg . x2 , seg . y2 ) ;
seg . x = pt . x ;
seg . y = pt . y ;
seg . x1 = pt1 . x ;
seg . y1 = pt1 . y ;
seg . x2 = pt2 . x ;
seg . y2 = pt2 . y ;
seg . r1 = scalew ( seg . r1 ) ,
seg . r2 = scaleh ( seg . r2 ) ;
}
else { // relative
seg . x = scalew ( seg . x ) ;
seg . y = scaleh ( seg . y ) ;
seg . x1 = scalew ( seg . x1 ) ;
seg . y1 = scaleh ( seg . y1 ) ;
seg . x2 = scalew ( seg . x2 ) ;
seg . y2 = scaleh ( seg . y2 ) ;
seg . r1 = scalew ( seg . r1 ) ,
seg . r2 = scaleh ( seg . r2 ) ;
}
// tracks the current position (for H,V commands)
if ( seg . x ) currentpt . x = seg . x ;
if ( seg . y ) currentpt . y = seg . y ;
} // for each segment
break ;
} // switch on element type to get initial values
// now we have a set of changes and an applied reduced transform list
// we apply the changes directly to the DOM
// TODO: merge this switch with the above one and optimize
switch ( selected . tagName )
{
case "foreignObject" :
case "rect" :
case "image" :
changes . x = changes . x - 0 + Math . min ( 0 , changes . width ) ;
changes . y = changes . y - 0 + Math . min ( 0 , changes . height ) ;
changes . width = Math . abs ( changes . width ) ;
changes . height = Math . abs ( changes . height ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping && selected . parentNode . parentNode . localName == "svg" ) {
changes . x = Utils . snapToGrid ( changes . x ) ;
changes . y = Utils . snapToGrid ( changes . y ) ;
changes . width = Utils . snapToGrid ( changes . width ) ;
changes . height = Utils . snapToGrid ( changes . height ) ;
}
2010-07-20 07:59:47 -05:00
assignAttributes ( selected , changes , 1000 , true ) ;
break ;
case "ellipse" :
changes . rx = Math . abs ( changes . rx ) ;
changes . ry = Math . abs ( changes . ry ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping && selected . parentNode . parentNode . localName == "svg" ) {
changes . cx = Utils . snapToGrid ( changes . cx ) ;
changes . cy = Utils . snapToGrid ( changes . cy ) ;
changes . rx = Utils . snapToGrid ( changes . rx ) ;
changes . ry = Utils . snapToGrid ( changes . ry ) ;
}
2010-07-20 07:59:47 -05:00
case "circle" :
if ( changes . r ) changes . r = Math . abs ( changes . r ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping && selected . parentNode . parentNode . localName == "svg" ) {
changes . cx = Utils . snapToGrid ( changes . cx ) ;
changes . cy = Utils . snapToGrid ( changes . cy ) ;
changes . r = Utils . snapToGrid ( changes . r ) ;
}
2010-07-20 07:59:47 -05:00
case "line" :
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping && selected . parentNode . parentNode . localName == "svg" ) {
changes . x1 = Utils . snapToGrid ( changes . x1 ) ;
changes . y1 = Utils . snapToGrid ( changes . y1 ) ;
changes . x2 = Utils . snapToGrid ( changes . x2 ) ;
changes . y2 = Utils . snapToGrid ( changes . y2 ) ;
}
2010-07-20 07:59:47 -05:00
case "text" :
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping && selected . parentNode . parentNode . localName == "svg" ) {
changes . x = Utils . snapToGrid ( changes . x ) ;
changes . y = Utils . snapToGrid ( changes . y ) ;
}
2010-08-13 16:14:19 -05:00
case "use" :
2010-07-20 07:59:47 -05:00
assignAttributes ( selected , changes , 1000 , true ) ;
break ;
case "g" :
var gsvg = $ ( selected ) . data ( 'gsvg' ) ;
if ( gsvg ) {
assignAttributes ( gsvg , changes , 1000 , true ) ;
}
break ;
case "polyline" :
case "polygon" :
var len = changes [ "points" ] . length ;
var pstr = "" ;
for ( var i = 0 ; i < len ; ++ i ) {
var pt = changes [ "points" ] [ i ] ;
pstr += pt . x + "," + pt . y + " " ;
}
selected . setAttribute ( "points" , pstr ) ;
break ;
case "path" :
var dstr = "" ;
var len = changes [ "d" ] . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var seg = changes [ "d" ] [ i ] ;
var type = seg . type ;
dstr += pathMap [ type ] ;
switch ( type ) {
case 13 : // relative horizontal line (h)
case 12 : // absolute horizontal line (H)
dstr += seg . x + " " ;
break ;
case 15 : // relative vertical line (v)
case 14 : // absolute vertical line (V)
dstr += seg . y + " " ;
break ;
case 3 : // relative move (m)
case 5 : // relative line (l)
case 19 : // relative smooth quad (t)
case 2 : // absolute move (M)
case 4 : // absolute line (L)
case 18 : // absolute smooth quad (T)
dstr += seg . x + "," + seg . y + " " ;
break ;
case 7 : // relative cubic (c)
case 6 : // absolute cubic (C)
dstr += seg . x1 + "," + seg . y1 + " " + seg . x2 + "," + seg . y2 + " " +
seg . x + "," + seg . y + " " ;
break ;
case 9 : // relative quad (q)
case 8 : // absolute quad (Q)
dstr += seg . x1 + "," + seg . y1 + " " + seg . x + "," + seg . y + " " ;
break ;
case 11 : // relative elliptical arc (a)
case 10 : // absolute elliptical arc (A)
dstr += seg . r1 + "," + seg . r2 + " " + seg . angle + " " + Number ( seg . largeArcFlag ) +
" " + Number ( seg . sweepFlag ) + " " + seg . x + "," + seg . y + " " ;
break ;
case 17 : // relative smooth cubic (s)
case 16 : // absolute smooth cubic (S)
dstr += seg . x2 + "," + seg . y2 + " " + seg . x + "," + seg . y + " " ;
break ;
}
}
selected . setAttribute ( "d" , dstr ) ;
break ;
}
} ;
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
// Function: updateClipPath
// Updates a <clipPath>s values based on the given translation of an element
//
// Parameters:
// attr - The clip-path attribute value with the clipPath's ID
// tx - The translation's x value
// ty - The translation's y value
var updateClipPath = function ( attr , tx , ty ) {
var id = getUrlFromAttr ( attr ) . substr ( 1 ) ;
var path = getElem ( id ) . firstChild ;
var cp _xform = getTransformList ( path ) ;
var newxlate = svgroot . createSVGTransform ( ) ;
newxlate . setTranslate ( tx , ty ) ;
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
cp _xform . appendItem ( newxlate ) ;
// Update clipPath's dimensions
recalculateDimensions ( path ) ;
}
// Function: recalculateDimensions
// Decides the course of action based on the element's transform list
//
// Parameters:
// selected - The DOM element to recalculate
//
// Returns:
// Undo command object with the resulting change
var recalculateDimensions = this . recalculateDimensions = function ( selected ) {
if ( selected == null ) return null ;
var tlist = getTransformList ( selected ) ;
// remove any unnecessary transforms
if ( tlist && tlist . numberOfItems > 0 ) {
var k = tlist . numberOfItems ;
while ( k -- ) {
var xform = tlist . getItem ( k ) ;
if ( xform . type == 0 ) {
tlist . removeItem ( k ) ;
}
// remove identity matrices
else if ( xform . type == 1 ) {
if ( isIdentity ( xform . matrix ) ) {
tlist . removeItem ( k ) ;
}
}
// remove zero-degree rotations
else if ( xform . type == 4 ) {
if ( xform . angle == 0 ) {
tlist . removeItem ( k ) ;
}
}
}
// End here if all it has is a rotation
if ( tlist . numberOfItems == 1 && getRotationAngle ( selected ) ) return null ;
}
// if this element had no transforms, we are done
if ( ! tlist || tlist . numberOfItems == 0 ) {
selected . removeAttribute ( "transform" ) ;
return null ;
}
// Grouped SVG element
var gsvg = $ ( selected ) . data ( 'gsvg' ) ;
// we know we have some transforms, so set up return variable
var batchCmd = new BatchCommand ( "Transform" ) ;
// store initial values that will be affected by reducing the transform list
var changes = { } , initial = null , attrs = [ ] ;
switch ( selected . tagName )
{
case "line" :
attrs = [ "x1" , "y1" , "x2" , "y2" ] ;
break ;
case "circle" :
attrs = [ "cx" , "cy" , "r" ] ;
break ;
case "ellipse" :
attrs = [ "cx" , "cy" , "rx" , "ry" ] ;
break ;
case "foreignObject" :
case "rect" :
case "image" :
attrs = [ "width" , "height" , "x" , "y" ] ;
break ;
case "use" :
case "text" :
attrs = [ "x" , "y" ] ;
break ;
case "polygon" :
case "polyline" :
initial = { } ;
initial [ "points" ] = selected . getAttribute ( "points" ) ;
var list = selected . points ;
var len = list . numberOfItems ;
changes [ "points" ] = new Array ( len ) ;
for ( var i = 0 ; i < len ; ++ i ) {
var pt = list . getItem ( i ) ;
changes [ "points" ] [ i ] = { x : pt . x , y : pt . y } ;
}
break ;
case "path" :
initial = { } ;
initial [ "d" ] = selected . getAttribute ( "d" ) ;
changes [ "d" ] = selected . getAttribute ( "d" ) ;
break ;
} // switch on element type to get initial values
if ( attrs . length ) {
changes = $ ( selected ) . attr ( attrs ) ;
$ . each ( changes , function ( attr , val ) {
changes [ attr ] = convertToNum ( attr , val ) ;
} ) ;
} else if ( gsvg ) {
// GSVG exception
changes = {
x : $ ( gsvg ) . attr ( 'x' ) || 0 ,
y : $ ( gsvg ) . attr ( 'y' ) || 0
} ;
}
// if we haven't created an initial array in polygon/polyline/path, then
// make a copy of initial values and include the transform
if ( initial == null ) {
initial = $ . extend ( true , { } , changes ) ;
$ . each ( initial , function ( attr , val ) {
initial [ attr ] = convertToNum ( attr , val ) ;
} ) ;
}
// save the start transform value too
initial [ "transform" ] = start _transform ? start _transform : "" ;
// if it's a regular group, we have special processing to flatten transforms
if ( ( selected . tagName == "g" && ! gsvg ) || selected . tagName == "a" ) {
var box = getBBox ( selected ) ,
oldcenter = { x : box . x + box . width / 2 , y : box . y + box . height / 2 } ,
newcenter = transformPoint ( box . x + box . width / 2 , box . y + box . height / 2 ,
transformListToTransform ( tlist ) . matrix ) ,
m = svgroot . createSVGMatrix ( ) ;
// temporarily strip off the rotate and save the old center
var gangle = getRotationAngle ( selected ) ;
if ( gangle ) {
var a = gangle * Math . PI / 180 ;
if ( Math . abs ( a ) > ( 1.0 e - 10 ) ) {
var s = Math . sin ( a ) / ( 1 - Math . cos ( a ) ) ;
} else {
// FIXME: This blows up if the angle is exactly 0!
var s = 2 / a ;
}
for ( var i = 0 ; i < tlist . numberOfItems ; ++ i ) {
var xform = tlist . getItem ( i ) ;
if ( xform . type == 4 ) {
// extract old center through mystical arts
var rm = xform . matrix ;
oldcenter . y = ( s * rm . e + rm . f ) / 2 ;
oldcenter . x = ( rm . e - s * rm . f ) / 2 ;
tlist . removeItem ( i ) ;
break ;
}
}
}
var tx = 0 , ty = 0 ,
operation = 0 ,
N = tlist . numberOfItems ;
if ( N ) {
var first _m = tlist . getItem ( 0 ) . matrix ;
}
// first, if it was a scale then the second-last transform will be it
if ( N >= 3 && tlist . getItem ( N - 2 ) . type == 3 &&
tlist . getItem ( N - 3 ) . type == 2 && tlist . getItem ( N - 1 ) . type == 2 )
{
operation = 3 ; // scale
// if the children are unrotated, pass the scale down directly
// otherwise pass the equivalent matrix() down directly
var tm = tlist . getItem ( N - 3 ) . matrix ,
sm = tlist . getItem ( N - 2 ) . matrix ,
tmn = tlist . getItem ( N - 1 ) . matrix ;
var children = selected . childNodes ;
var c = children . length ;
while ( c -- ) {
var child = children . item ( c ) ;
tx = 0 ;
ty = 0 ;
if ( child . nodeType == 1 ) {
var childTlist = getTransformList ( child ) ;
// some children might not have a transform (<metadata>, <defs>, etc)
if ( ! childTlist ) continue ;
var m = transformListToTransform ( childTlist ) . matrix ;
2010-08-13 16:14:19 -05:00
// Convert a matrix to a scale if applicable
// if(hasMatrixTransform(childTlist) && childTlist.numberOfItems == 1) {
// if(m.b==0 && m.c==0 && m.e==0 && m.f==0) {
// childTlist.removeItem(0);
// var translateOrigin = svgroot.createSVGTransform(),
// scale = svgroot.createSVGTransform(),
// translateBack = svgroot.createSVGTransform();
// translateOrigin.setTranslate(0, 0);
// scale.setScale(m.a, m.d);
// translateBack.setTranslate(0, 0);
// childTlist.appendItem(translateBack);
// childTlist.appendItem(scale);
// childTlist.appendItem(translateOrigin);
// }
// }
2010-07-20 07:59:47 -05:00
var angle = getRotationAngle ( child ) ;
var old _start _transform = start _transform ;
var childxforms = [ ] ;
start _transform = child . getAttribute ( "transform" ) ;
if ( angle || hasMatrixTransform ( childTlist ) ) {
var e2t = svgroot . createSVGTransform ( ) ;
e2t . setMatrix ( matrixMultiply ( tm , sm , tmn , m ) ) ;
childTlist . clear ( ) ;
childTlist . appendItem ( e2t ) ;
childxforms . push ( e2t ) ;
}
// if not rotated or skewed, push the [T][S][-T] down to the child
else {
// update the transform list with translate,scale,translate
// slide the [T][S][-T] from the front to the back
// [T][S][-T][M] = [M][T2][S2][-T2]
// (only bringing [-T] to the right of [M])
// [T][S][-T][M] = [T][S][M][-T2]
// [-T2] = [M_inv][-T][M]
var t2n = matrixMultiply ( m . inverse ( ) , tmn , m ) ;
// [T2] is always negative translation of [-T2]
var t2 = svgroot . createSVGMatrix ( ) ;
t2 . e = - t2n . e ;
t2 . f = - t2n . f ;
// [T][S][-T][M] = [M][T2][S2][-T2]
// [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv]
var s2 = matrixMultiply ( t2 . inverse ( ) , m . inverse ( ) , tm , sm , tmn , m , t2n . inverse ( ) ) ;
var translateOrigin = svgroot . createSVGTransform ( ) ,
scale = svgroot . createSVGTransform ( ) ,
translateBack = svgroot . createSVGTransform ( ) ;
translateOrigin . setTranslate ( t2n . e , t2n . f ) ;
scale . setScale ( s2 . a , s2 . d ) ;
translateBack . setTranslate ( t2 . e , t2 . f ) ;
childTlist . appendItem ( translateBack ) ;
childTlist . appendItem ( scale ) ;
childTlist . appendItem ( translateOrigin ) ;
childxforms . push ( translateBack ) ;
childxforms . push ( scale ) ;
childxforms . push ( translateOrigin ) ;
logMatrix ( translateBack . matrix ) ;
logMatrix ( scale . matrix ) ;
} // not rotated
batchCmd . addSubCommand ( recalculateDimensions ( child ) ) ;
// TODO: If any <use> have this group as a parent and are
// referencing this child, then we need to impose a reverse
// scale on it so that when it won't get double-translated
2010-04-01 23:56:21 -05:00
// var uses = selected.getElementsByTagNameNS(svgns, "use");
// var href = "#"+child.id;
// var u = uses.length;
// while (u--) {
// var useElem = uses.item(u);
2010-09-10 15:19:23 -05:00
// if(href == getHref(useElem)) {
2010-04-01 23:56:21 -05:00
// var usexlate = svgroot.createSVGTransform();
// usexlate.setTranslate(-tx,-ty);
2010-07-20 07:59:47 -05:00
// getTransformList(useElem).insertItemBefore(usexlate,0);
2010-04-01 23:56:21 -05:00
// batchCmd.addSubCommand( recalculateDimensions(useElem) );
// }
// }
2010-02-05 21:36:35 -06:00
start _transform = old _start _transform ;
2010-07-20 07:59:47 -05:00
} // element
} // for each child
// Remove these transforms from group
tlist . removeItem ( N - 1 ) ;
tlist . removeItem ( N - 2 ) ;
tlist . removeItem ( N - 3 ) ;
}
else if ( N >= 3 && tlist . getItem ( N - 1 ) . type == 1 )
{
operation = 3 ; // scale
m = transformListToTransform ( tlist ) . matrix ;
var e2t = svgroot . createSVGTransform ( ) ;
e2t . setMatrix ( m ) ;
tlist . clear ( ) ;
tlist . appendItem ( e2t ) ;
}
// next, check if the first transform was a translate
// if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
else if ( ( N == 1 || ( N > 1 && tlist . getItem ( 1 ) . type != 3 ) ) &&
tlist . getItem ( 0 ) . type == 2 )
{
operation = 2 ; // translate
var T _M = transformListToTransform ( tlist ) . matrix ;
tlist . removeItem ( 0 ) ;
var M _inv = transformListToTransform ( tlist ) . matrix . inverse ( ) ;
var M2 = matrixMultiply ( M _inv , T _M ) ;
tx = M2 . e ;
ty = M2 . f ;
if ( tx != 0 || ty != 0 ) {
// we pass the translates down to the individual children
var children = selected . childNodes ;
var c = children . length ;
var clipPaths _done = [ ] ;
2010-02-05 21:36:35 -06:00
while ( c -- ) {
var child = children . item ( c ) ;
if ( child . nodeType == 1 ) {
2010-07-20 07:59:47 -05:00
// Check if child has clip-path
if ( child . getAttribute ( 'clip-path' ) ) {
// tx, ty
var attr = child . getAttribute ( 'clip-path' ) ;
if ( $ . inArray ( attr , clipPaths _done ) === - 1 ) {
updateClipPath ( attr , tx , ty ) ;
clipPaths _done . push ( attr ) ;
}
}
2010-02-05 21:36:35 -06:00
var old _start _transform = start _transform ;
start _transform = child . getAttribute ( "transform" ) ;
2010-07-20 07:59:47 -05:00
var childTlist = getTransformList ( child ) ;
// some children might not have a transform (<metadata>, <defs>, etc)
if ( childTlist ) {
2010-02-05 21:36:35 -06:00
var newxlate = svgroot . createSVGTransform ( ) ;
newxlate . setTranslate ( tx , ty ) ;
childTlist . insertItemBefore ( newxlate , 0 ) ;
batchCmd . addSubCommand ( recalculateDimensions ( child ) ) ;
2010-07-20 07:59:47 -05:00
// If any <use> have this group as a parent and are
// referencing this child, then impose a reverse translate on it
// so that when it won't get double-translated
var uses = selected . getElementsByTagNameNS ( svgns , "use" ) ;
var href = "#" + child . id ;
var u = uses . length ;
while ( u -- ) {
var useElem = uses . item ( u ) ;
2010-09-10 15:19:23 -05:00
if ( href == getHref ( useElem ) ) {
2010-07-20 07:59:47 -05:00
var usexlate = svgroot . createSVGTransform ( ) ;
usexlate . setTranslate ( - tx , - ty ) ;
getTransformList ( useElem ) . insertItemBefore ( usexlate , 0 ) ;
batchCmd . addSubCommand ( recalculateDimensions ( useElem ) ) ;
}
}
2010-02-05 21:36:35 -06:00
start _transform = old _start _transform ;
}
}
}
2010-07-20 07:59:47 -05:00
clipPaths _done = [ ] ;
start _transform = old _start _transform ;
}
}
// else, a matrix imposition from a parent group
// keep pushing it down to the children
else if ( N == 1 && tlist . getItem ( 0 ) . type == 1 && ! gangle ) {
operation = 1 ;
var m = tlist . getItem ( 0 ) . matrix ,
children = selected . childNodes ,
c = children . length ;
while ( c -- ) {
var child = children . item ( c ) ;
if ( child . nodeType == 1 ) {
var old _start _transform = start _transform ;
start _transform = child . getAttribute ( "transform" ) ;
var childTlist = getTransformList ( child ) ;
2010-08-13 16:14:19 -05:00
if ( ! childTlist ) continue ;
2010-07-20 07:59:47 -05:00
var em = matrixMultiply ( m , transformListToTransform ( childTlist ) . matrix ) ;
var e2m = svgroot . createSVGTransform ( ) ;
e2m . setMatrix ( em ) ;
childTlist . clear ( ) ;
childTlist . appendItem ( e2m , 0 ) ;
batchCmd . addSubCommand ( recalculateDimensions ( child ) ) ;
start _transform = old _start _transform ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
tlist . clear ( ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// else it was just a rotate
2010-02-05 21:36:35 -06:00
else {
2010-07-20 07:59:47 -05:00
if ( gangle ) {
var newRot = svgroot . createSVGTransform ( ) ;
newRot . setRotate ( gangle , newcenter . x , newcenter . y ) ;
tlist . insertItemBefore ( newRot , 0 ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
if ( tlist . numberOfItems == 0 ) {
selected . removeAttribute ( "transform" ) ;
2010-05-11 00:38:21 -05:00
}
2010-07-20 07:59:47 -05:00
return null ;
}
// if it was a translate, put back the rotate at the new center
if ( operation == 2 ) {
if ( gangle ) {
newcenter = {
x : oldcenter . x + first _m . e ,
y : oldcenter . y + first _m . f
} ;
2010-05-11 00:38:21 -05:00
2010-07-20 07:59:47 -05:00
var newRot = svgroot . createSVGTransform ( ) ;
newRot . setRotate ( gangle , newcenter . x , newcenter . y ) ;
tlist . insertItemBefore ( newRot , 0 ) ;
}
}
// if it was a resize
else if ( operation == 3 ) {
var m = transformListToTransform ( tlist ) . matrix ;
var roldt = svgroot . createSVGTransform ( ) ;
roldt . setRotate ( gangle , oldcenter . x , oldcenter . y ) ;
var rold = roldt . matrix ;
var rnew = svgroot . createSVGTransform ( ) ;
rnew . setRotate ( gangle , newcenter . x , newcenter . y ) ;
var rnew _inv = rnew . matrix . inverse ( ) ,
m _inv = m . inverse ( ) ,
extrat = matrixMultiply ( m _inv , rnew _inv , rold , m ) ;
tx = extrat . e ;
ty = extrat . f ;
if ( tx != 0 || ty != 0 ) {
// now push this transform down to the children
// we pass the translates down to the individual children
var children = selected . childNodes ;
var c = children . length ;
while ( c -- ) {
var child = children . item ( c ) ;
if ( child . nodeType == 1 ) {
var old _start _transform = start _transform ;
start _transform = child . getAttribute ( "transform" ) ;
var childTlist = getTransformList ( child ) ;
var newxlate = svgroot . createSVGTransform ( ) ;
newxlate . setTranslate ( tx , ty ) ;
childTlist . insertItemBefore ( newxlate , 0 ) ;
batchCmd . addSubCommand ( recalculateDimensions ( child ) ) ;
start _transform = old _start _transform ;
}
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
if ( gangle ) {
tlist . insertItemBefore ( rnew , 0 ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
// else, it's a non-group
else {
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// FIXME: box might be null for some elements (<metadata> etc), need to handle this
var box = getBBox ( selected ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Paths (and possbly other shapes) will have no BBox while still in <defs>,
// but we still may need to recalculate them (see issue 595).
// TODO: Figure out how to get BBox from these elements in case they
// have a rotation transform
if ( ! box && selected . tagName != 'path' ) return null ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var m = svgroot . createSVGMatrix ( ) ,
// temporarily strip off the rotate and save the old center
angle = getRotationAngle ( selected ) ;
if ( angle ) {
var oldcenter = { x : box . x + box . width / 2 , y : box . y + box . height / 2 } ,
newcenter = transformPoint ( box . x + box . width / 2 , box . y + box . height / 2 ,
transformListToTransform ( tlist ) . matrix ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var a = angle * Math . PI / 180 ;
if ( Math . abs ( a ) > ( 1.0 e - 10 ) ) {
var s = Math . sin ( a ) / ( 1 - Math . cos ( a ) ) ;
} else {
// FIXME: This blows up if the angle is exactly 0!
var s = 2 / a ;
}
for ( var i = 0 ; i < tlist . numberOfItems ; ++ i ) {
var xform = tlist . getItem ( i ) ;
if ( xform . type == 4 ) {
// extract old center through mystical arts
var rm = xform . matrix ;
oldcenter . y = ( s * rm . e + rm . f ) / 2 ;
oldcenter . x = ( rm . e - s * rm . f ) / 2 ;
tlist . removeItem ( i ) ;
break ;
2010-02-05 21:36:35 -06:00
}
}
}
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
// 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition
var operation = 0 ;
var N = tlist . numberOfItems ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Check if it has a gradient with userSpaceOnUse, in which case
// adjust it by recalculating the matrix transform.
// TODO: Make this work in Webkit using SVGEditTransformList
if ( ! isWebkit ) {
var fill = selected . getAttribute ( 'fill' ) ;
if ( fill && fill . indexOf ( 'url(' ) === 0 ) {
var grad = getElem ( getUrlFromAttr ( fill ) . substr ( 1 ) ) ;
if ( grad . getAttribute ( 'gradientUnits' ) === 'userSpaceOnUse' ) {
//Update the userSpaceOnUse element
var grad = $ ( grad ) ;
m = transformListToTransform ( tlist ) . matrix ;
var gtlist = getTransformList ( grad [ 0 ] ) ;
var gmatrix = transformListToTransform ( gtlist ) . matrix ;
m = matrixMultiply ( m , gmatrix ) ;
var m _str = "matrix(" + [ m . a , m . b , m . c , m . d , m . e , m . f ] . join ( "," ) + ")" ;
grad . attr ( 'gradientTransform' , m _str ) ;
}
}
}
// first, if it was a scale of a non-skewed element, then the second-last
// transform will be the [S]
// if we had [M][T][S][T] we want to extract the matrix equivalent of
// [T][S][T] and push it down to the element
if ( N >= 3 && tlist . getItem ( N - 2 ) . type == 3 &&
2010-08-13 16:14:19 -05:00
tlist . getItem ( N - 3 ) . type == 2 && tlist . getItem ( N - 1 ) . type == 2 )
// Removed this so a <use> with a given [T][S][T] would convert to a matrix.
// Is that bad?
// && selected.nodeName != "use"
2010-07-20 07:59:47 -05:00
{
operation = 3 ; // scale
m = transformListToTransform ( tlist , N - 3 , N - 1 ) . matrix ;
tlist . removeItem ( N - 1 ) ;
tlist . removeItem ( N - 2 ) ;
tlist . removeItem ( N - 3 ) ;
} // if we had [T][S][-T][M], then this was a skewed element being resized
// Thus, we simply combine it all into one matrix
else if ( N == 4 && tlist . getItem ( N - 1 ) . type == 1 ) {
operation = 3 ; // scale
m = transformListToTransform ( tlist ) . matrix ;
var e2t = svgroot . createSVGTransform ( ) ;
e2t . setMatrix ( m ) ;
tlist . clear ( ) ;
tlist . appendItem ( e2t ) ;
// reset the matrix so that the element is not re-mapped
m = svgroot . createSVGMatrix ( ) ;
} // if we had [R][T][S][-T][M], then this was a rotated matrix-element
// if we had [T1][M] we want to transform this into [M][T2]
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
// down to the element
else if ( ( N == 1 || ( N > 1 && tlist . getItem ( 1 ) . type != 3 ) ) &&
tlist . getItem ( 0 ) . type == 2 )
{
operation = 2 ; // translate
var oldxlate = tlist . getItem ( 0 ) . matrix ,
meq = transformListToTransform ( tlist , 1 ) . matrix ,
meq _inv = meq . inverse ( ) ;
m = matrixMultiply ( meq _inv , oldxlate , meq ) ;
tlist . removeItem ( 0 ) ;
}
// else if this child now has a matrix imposition (from a parent group)
// we might be able to simplify
else if ( N == 1 && tlist . getItem ( 0 ) . type == 1 && ! angle ) {
// Remap all point-based elements
m = transformListToTransform ( tlist ) . matrix ;
switch ( selected . tagName ) {
case 'line' :
changes = $ ( selected ) . attr ( [ "x1" , "y1" , "x2" , "y2" ] ) ;
case 'polyline' :
case 'polygon' :
changes . points = selected . getAttribute ( "points" ) ;
if ( changes . points ) {
var list = selected . points ;
var len = list . numberOfItems ;
changes . points = new Array ( len ) ;
for ( var i = 0 ; i < len ; ++ i ) {
var pt = list . getItem ( i ) ;
changes . points [ i ] = { x : pt . x , y : pt . y } ;
}
}
case 'path' :
changes . d = selected . getAttribute ( "d" ) ;
operation = 1 ;
tlist . clear ( ) ;
break ;
default :
break ;
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// if it was a rotation, put the rotate back and return without a command
// (this function has zero work to do for a rotate())
2010-02-05 21:36:35 -06:00
else {
2010-07-20 07:59:47 -05:00
operation = 4 ; // rotation
if ( angle ) {
var newRot = svgroot . createSVGTransform ( ) ;
newRot . setRotate ( angle , newcenter . x , newcenter . y ) ;
tlist . insertItemBefore ( newRot , 0 ) ;
}
if ( tlist . numberOfItems == 0 ) {
selected . removeAttribute ( "transform" ) ;
}
return null ;
}
// if it was a translate or resize, we need to remap the element and absorb the xform
if ( operation == 1 || operation == 2 || operation == 3 ) {
remapElement ( selected , changes , m ) ;
} // if we are remapping
// if it was a translate, put back the rotate at the new center
if ( operation == 2 ) {
if ( angle ) {
2010-09-10 15:19:23 -05:00
if ( ! hasMatrixTransform ( tlist ) ) {
newcenter = {
x : oldcenter . x + m . e ,
y : oldcenter . y + m . f
} ;
}
2010-07-20 07:59:47 -05:00
var newRot = svgroot . createSVGTransform ( ) ;
newRot . setRotate ( angle , newcenter . x , newcenter . y ) ;
tlist . insertItemBefore ( newRot , 0 ) ;
}
}
// [Rold][M][T][S][-T] became [Rold][M]
// we want it to be [Rnew][M][Tr] where Tr is the
// translation required to re-center it
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
else if ( operation == 3 && angle ) {
var m = transformListToTransform ( tlist ) . matrix ;
var roldt = svgroot . createSVGTransform ( ) ;
roldt . setRotate ( angle , oldcenter . x , oldcenter . y ) ;
var rold = roldt . matrix ;
var rnew = svgroot . createSVGTransform ( ) ;
rnew . setRotate ( angle , newcenter . x , newcenter . y ) ;
var rnew _inv = rnew . matrix . inverse ( ) ;
var m _inv = m . inverse ( ) ;
var extrat = matrixMultiply ( m _inv , rnew _inv , rold , m ) ;
remapElement ( selected , changes , extrat ) ;
if ( angle ) {
tlist . insertItemBefore ( rnew , 0 ) ;
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} // a non-group
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// if the transform list has been emptied, remove it
if ( tlist . numberOfItems == 0 ) {
selected . removeAttribute ( "transform" ) ;
}
batchCmd . addSubCommand ( new ChangeElementCommand ( selected , initial ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
return batchCmd ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Root Current Transformation Matrix in user units
var root _sctm = null ;
// Function: transformPoint
// A (hopefully) quicker function to transform a point by a matrix
// (this function avoids any DOM calls and just does the math)
//
// Parameters:
// x - Float representing the x coordinate
// y - Float representing the y coordinate
// m - Matrix object to transform the point with
// Returns a x,y object representing the transformed point
var transformPoint = function ( x , y , m ) {
return { x : m . a * x + m . c * y + m . e , y : m . b * x + m . d * y + m . f } ;
} ;
// Function: isIdentity
// Helper function to check if the matrix performs no actual transform
// (i.e. exists for identity purposes)
//
// Parameters:
// m - The matrix object to check
//
// Returns:
// Boolean indicating whether or not the matrix is 1,0,0,1,0,0
var isIdentity = function ( m ) {
return ( m . a == 1 && m . b == 0 && m . c == 0 && m . d == 1 && m . e == 0 && m . f == 0 ) ;
}
// matrixMultiply() is provided because WebKit didn't implement multiply() correctly
// on the SVGMatrix interface. See https://bugs.webkit.org/show_bug.cgi?id=16062
// Function: matrixMultiply
// This function tries to return a SVGMatrix that is the multiplication m1*m2.
// We also round to zero when it's near zero
//
// Parameters:
// >= 2 Matrix objects to multiply
//
// Returns:
// The matrix object resulting from the calculation
var matrixMultiply = this . matrixMultiply = function ( ) {
var NEAR _ZERO = 1e-14 ,
multi2 = function ( m1 , m2 ) {
var m = svgroot . createSVGMatrix ( ) ;
m . a = m1 . a * m2 . a + m1 . c * m2 . b ;
m . b = m1 . b * m2 . a + m1 . d * m2 . b ,
m . c = m1 . a * m2 . c + m1 . c * m2 . d ,
m . d = m1 . b * m2 . c + m1 . d * m2 . d ,
m . e = m1 . a * m2 . e + m1 . c * m2 . f + m1 . e ,
m . f = m1 . b * m2 . e + m1 . d * m2 . f + m1 . f ;
return m ;
} ,
args = arguments , i = args . length , m = args [ i - 1 ] ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
while ( i -- > 1 ) {
var m1 = args [ i - 1 ] ;
m = multi2 ( m1 , m ) ;
}
if ( Math . abs ( m . a ) < NEAR _ZERO ) m . a = 0 ;
if ( Math . abs ( m . b ) < NEAR _ZERO ) m . b = 0 ;
if ( Math . abs ( m . c ) < NEAR _ZERO ) m . c = 0 ;
if ( Math . abs ( m . d ) < NEAR _ZERO ) m . d = 0 ;
if ( Math . abs ( m . e ) < NEAR _ZERO ) m . e = 0 ;
if ( Math . abs ( m . f ) < NEAR _ZERO ) m . f = 0 ;
return m ;
}
// Function: transformListToTransform
// This returns a single matrix Transform for a given Transform List
// (this is the equivalent of SVGTransformList.consolidate() but unlike
// that method, this one does not modify the actual SVGTransformList)
// This function is very liberal with its min,max arguments
//
// Parameters:
// tlist - The transformlist object
// min - Optional integer indicating start transform position
// max - Optional integer indicating end transform position
//
// Returns:
// A single matrix transform object
var transformListToTransform = this . transformListToTransform = function ( tlist , min , max ) {
if ( tlist == null ) {
// Or should tlist = null have been prevented before this?
return svgroot . createSVGTransformFromMatrix ( svgroot . createSVGMatrix ( ) ) ;
}
var min = min == undefined ? 0 : min ;
var max = max == undefined ? ( tlist . numberOfItems - 1 ) : max ;
min = parseInt ( min ) ;
max = parseInt ( max ) ;
if ( min > max ) { var temp = max ; max = min ; min = temp ; }
var m = svgroot . createSVGMatrix ( ) ;
for ( var i = min ; i <= max ; ++ i ) {
// if our indices are out of range, just use a harmless identity matrix
var mtom = ( i >= 0 && i < tlist . numberOfItems ?
tlist . getItem ( i ) . matrix :
svgroot . createSVGMatrix ( ) ) ;
m = matrixMultiply ( m , mtom ) ;
}
return svgroot . createSVGTransformFromMatrix ( m ) ;
} ;
// Function: hasMatrixTransform
// See if the given transformlist includes a non-indentity matrix transform
//
// Parameters:
// tlist - The transformlist to check
//
// Returns:
// Boolean on whether or not a matrix transform was found
var hasMatrixTransform = this . hasMatrixTransform = function ( tlist ) {
if ( ! tlist ) return false ;
var num = tlist . numberOfItems ;
while ( num -- ) {
var xform = tlist . getItem ( num ) ;
if ( xform . type == 1 && ! isIdentity ( xform . matrix ) ) return true ;
}
return false ;
}
// Function: getMatrix
// Get the matrix object for a given element
//
// Parameters:
// elem - The DOM element to check
//
// Returns:
// The matrix object associated with the element's transformlist
var getMatrix = function ( elem ) {
var tlist = getTransformList ( elem ) ;
return transformListToTransform ( tlist ) . matrix ;
}
// Function: transformBox
// Transforms a rectangle based on the given matrix
//
// Parameters:
// l - Float with the box's left coordinate
// t - Float with the box's top coordinate
// w - Float with the box width
// h - Float with the box height
// m - Matrix object to transform the box by
//
// Returns:
// An object with the following values:
// * tl - The top left coordinate (x,y object)
// * tr - The top right coordinate (x,y object)
// * bl - The bottom left coordinate (x,y object)
// * br - The bottom right coordinate (x,y object)
// * aabox - Object with the following values:
// * Float with the axis-aligned x coordinate
// * Float with the axis-aligned y coordinate
// * Float with the axis-aligned width coordinate
// * Float with the axis-aligned height coordinate
var transformBox = this . transformBox = function ( l , t , w , h , m ) {
var topleft = { x : l , y : t } ,
topright = { x : ( l + w ) , y : t } ,
botright = { x : ( l + w ) , y : ( t + h ) } ,
botleft = { x : l , y : ( t + h ) } ;
topleft = transformPoint ( topleft . x , topleft . y , m ) ;
var minx = topleft . x ,
maxx = topleft . x ,
miny = topleft . y ,
maxy = topleft . y ;
topright = transformPoint ( topright . x , topright . y , m ) ;
minx = Math . min ( minx , topright . x ) ;
maxx = Math . max ( maxx , topright . x ) ;
miny = Math . min ( miny , topright . y ) ;
maxy = Math . max ( maxy , topright . y ) ;
botleft = transformPoint ( botleft . x , botleft . y , m ) ;
minx = Math . min ( minx , botleft . x ) ;
maxx = Math . max ( maxx , botleft . x ) ;
miny = Math . min ( miny , botleft . y ) ;
maxy = Math . max ( maxy , botleft . y ) ;
botright = transformPoint ( botright . x , botright . y , m ) ;
minx = Math . min ( minx , botright . x ) ;
maxx = Math . max ( maxx , botright . x ) ;
miny = Math . min ( miny , botright . y ) ;
maxy = Math . max ( maxy , botright . y ) ;
return { tl : topleft , tr : topright , bl : botleft , br : botright ,
aabox : { x : minx , y : miny , width : ( maxx - minx ) , height : ( maxy - miny ) } } ;
} ;
// Group: Selection
// Function: clearSelection
// Clears the selection. The 'selected' handler is then called.
// Parameters:
// noCall - Optional boolean that when true does not call the "selected" handler
var clearSelection = this . clearSelection = function ( noCall ) {
if ( selectedElements [ 0 ] != null ) {
var len = selectedElements . length ;
2010-02-05 21:36:35 -06:00
for ( var i = 0 ; i < len ; ++ i ) {
var elem = selectedElements [ i ] ;
2010-07-20 07:59:47 -05:00
if ( elem == null ) break ;
selectorManager . releaseSelector ( elem ) ;
selectedElements [ i ] = null ;
}
selectedBBoxes [ 0 ] = null ;
}
if ( ! noCall ) call ( "selected" , selectedElements ) ;
} ;
// TODO: do we need to worry about selectedBBoxes here?
// Function: addToSelection
// Adds a list of elements to the selection. The 'selected' handler is then called.
//
// Parameters:
// elemsToAdd - an array of DOM elements to add to the selection
// showGrips - a boolean flag indicating whether the resize grips should be shown
var addToSelection = this . addToSelection = function ( elemsToAdd , showGrips ) {
if ( elemsToAdd . length == 0 ) { return ; }
// find the first null in our selectedElements array
var j = 0 ;
while ( j < selectedElements . length ) {
if ( selectedElements [ j ] == null ) {
break ;
}
++ j ;
}
// now add each element consecutively
var i = elemsToAdd . length ;
while ( i -- ) {
var elem = elemsToAdd [ i ] ;
if ( ! elem || ! getBBox ( elem ) ) continue ;
// if it's not already there, add it
if ( selectedElements . indexOf ( elem ) == - 1 ) {
selectedElements [ j ] = elem ;
// only the first selectedBBoxes element is ever used in the codebase these days
if ( j == 0 ) selectedBBoxes [ j ] = getBBox ( elem ) ;
j ++ ;
var sel = selectorManager . requestSelector ( elem ) ;
if ( selectedElements . length > 1 ) {
sel . showGrips ( false ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
if ( selectedElements [ 0 ] && selectedElements . length === 1 && selectedElements [ 0 ] . tagName == 'a' ) {
// Make "a" element's child be the selected element
selectedElements [ 0 ] = selectedElements [ 0 ] . firstChild ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
call ( "selected" , selectedElements ) ;
if ( showGrips || selectedElements . length == 1 ) {
selectorManager . requestSelector ( selectedElements [ 0 ] ) . showGrips ( true ) ;
}
else {
selectorManager . requestSelector ( selectedElements [ 0 ] ) . showGrips ( false ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// make sure the elements are in the correct order
// See: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
selectedElements . sort ( function ( a , b ) {
if ( a && b && a . compareDocumentPosition ) {
return 3 - ( b . compareDocumentPosition ( a ) & 6 ) ;
} else if ( a == null ) {
return 1 ;
}
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Make sure first elements are not null
while ( selectedElements [ 0 ] == null ) selectedElements . shift ( 0 ) ;
} ;
// TODO: could use slice here to make this faster?
// TODO: should the 'selected' handler
// Function: removeFromSelection
// Removes elements from the selection.
//
// Parameters:
// elemsToRemove - an array of elements to remove from selection
var removeFromSelection = this . removeFromSelection = function ( elemsToRemove ) {
if ( selectedElements [ 0 ] == null ) { return ; }
if ( elemsToRemove . length == 0 ) { return ; }
// find every element and remove it from our array copy
var newSelectedItems = new Array ( selectedElements . length ) ,
newSelectedBBoxes = new Array ( selectedBBoxes . length ) ,
j = 0 ,
len = selectedElements . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var elem = selectedElements [ i ] ;
if ( elem ) {
// keep the item
if ( elemsToRemove . indexOf ( elem ) == - 1 ) {
newSelectedItems [ j ] = elem ;
if ( j == 0 ) newSelectedBBoxes [ j ] = selectedBBoxes [ i ] ;
j ++ ;
2010-02-05 23:17:34 -06:00
}
2010-07-20 07:59:47 -05:00
else { // remove the item and its selector
selectorManager . releaseSelector ( elem ) ;
2010-02-05 23:17:34 -06:00
}
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
// the copy becomes the master now
selectedElements = newSelectedItems ;
selectedBBoxes = newSelectedBBoxes ;
} ;
// Function: selectAllInCurrentLayer
// Clears the selection, then adds all elements in the current layer to the selection.
// This function then fires the selected event.
this . selectAllInCurrentLayer = function ( ) {
if ( current _layer ) {
clearSelection ( ) ;
addToSelection ( $ ( current _layer ) . children ( ) ) ;
current _mode = "select" ;
call ( "selected" , selectedElements ) ;
2010-04-28 00:22:49 -05:00
}
2010-07-20 07:59:47 -05:00
} ;
// Function: smoothControlPoints
// Takes three points and creates a smoother line based on them
//
// Parameters:
// ct1 - Object with x and y values (first control point)
// ct2 - Object with x and y values (second control point)
// pt - Object with x and y values (third point)
//
// Returns:
// Array of two "smoothed" point objects
var smoothControlPoints = this . smoothControlPoints = function ( ct1 , ct2 , pt ) {
// each point must not be the origin
var x1 = ct1 . x - pt . x ,
y1 = ct1 . y - pt . y ,
x2 = ct2 . x - pt . x ,
y2 = ct2 . y - pt . y ;
if ( ( x1 != 0 || y1 != 0 ) && ( x2 != 0 || y2 != 0 ) ) {
var anglea = Math . atan2 ( y1 , x1 ) ,
angleb = Math . atan2 ( y2 , x2 ) ,
r1 = Math . sqrt ( x1 * x1 + y1 * y1 ) ,
r2 = Math . sqrt ( x2 * x2 + y2 * y2 ) ,
nct1 = svgroot . createSVGPoint ( ) ,
nct2 = svgroot . createSVGPoint ( ) ;
if ( anglea < 0 ) { anglea += 2 * Math . PI ; }
if ( angleb < 0 ) { angleb += 2 * Math . PI ; }
var angleBetween = Math . abs ( anglea - angleb ) ,
angleDiff = Math . abs ( Math . PI - angleBetween ) / 2 ;
var new _anglea , new _angleb ;
if ( anglea - angleb > 0 ) {
new _anglea = angleBetween < Math . PI ? ( anglea + angleDiff ) : ( anglea - angleDiff ) ;
new _angleb = angleBetween < Math . PI ? ( angleb - angleDiff ) : ( angleb + angleDiff ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
else {
new _anglea = angleBetween < Math . PI ? ( anglea - angleDiff ) : ( anglea + angleDiff ) ;
new _angleb = angleBetween < Math . PI ? ( angleb + angleDiff ) : ( angleb - angleDiff ) ;
2010-06-03 07:13:37 -05:00
}
2010-07-20 07:59:47 -05:00
// rotate the points
nct1 . x = r1 * Math . cos ( new _anglea ) + pt . x ;
nct1 . y = r1 * Math . sin ( new _anglea ) + pt . y ;
nct2 . x = r2 * Math . cos ( new _angleb ) + pt . x ;
nct2 . y = r2 * Math . sin ( new _angleb ) + pt . y ;
2010-06-03 07:13:37 -05:00
2010-07-20 07:59:47 -05:00
return [ nct1 , nct2 ] ;
}
return undefined ;
} ;
// Function: getMouseTarget
// Gets the desired element from a mouse event
//
// Parameters:
// evt - Event object from the mouse event
//
// Returns:
// DOM element we want
var getMouseTarget = this . getMouseTarget = function ( evt ) {
if ( evt == null ) {
return null ;
}
var mouse _target = evt . target ;
// if it was a <use>, Opera and WebKit return the SVGElementInstance
if ( mouse _target . correspondingUseElement )
mouse _target = mouse _target . correspondingUseElement ;
// for foreign content, go up until we find the foreignObject
// WebKit browsers set the mouse target to the svgcanvas div
if ( $ . inArray ( mouse _target . namespaceURI , [ mathns , htmlns ] ) != - 1 &&
mouse _target . id != "svgcanvas" )
{
while ( mouse _target . nodeName != "foreignObject" ) {
2010-06-03 07:13:37 -05:00
mouse _target = mouse _target . parentNode ;
}
2010-07-20 07:59:47 -05:00
}
// Get the desired mouse_target with jQuery selector-fu
// If it's root-like, select the root
if ( $ . inArray ( mouse _target , [ svgroot , container , svgcontent , current _layer ] ) !== - 1 ) {
return svgroot ;
}
var $target = $ ( mouse _target ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// If it's a selection grip, return the grip parent
if ( $target . closest ( '#selectorParentGroup' ) . length ) {
// While we could instead have just returned mouse_target,
// this makes it easier to indentify as being a selector grip
return selectorManager . selectorParentGroup ;
}
while ( mouse _target . parentNode !== current _layer ) {
mouse _target = mouse _target . parentNode ;
}
return mouse _target ;
//
// // go up until we hit a child of a layer
// while (mouse_target.parentNode.parentNode.tagName == 'g') {
// mouse_target = mouse_target.parentNode;
// }
// Webkit bubbles the mouse event all the way up to the div, so we
// set the mouse_target to the svgroot like the other browsers
// if (mouse_target.nodeName.toLowerCase() == "div") {
// mouse_target = svgroot;
// }
return mouse _target ;
} ;
2010-06-03 07:13:37 -05:00
2010-07-20 07:59:47 -05:00
// Mouse events
( function ( ) {
2010-09-10 15:19:23 -05:00
var off _x , off _y ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var d _attr = null ,
start _x = null ,
start _y = null ,
2010-09-10 15:19:23 -05:00
r _start _x = null ,
r _start _y = null ,
2010-07-20 07:59:47 -05:00
init _bbox = { } ,
freehand = {
minx : null ,
miny : null ,
maxx : null ,
maxy : null
} ;
// - when we are in a create mode, the element is added to the canvas
// but the action is not recorded until mousing up
// - when we are in select mode, select the element, remember the position
// and do nothing else
var mouseDown = function ( evt )
{
2010-09-10 15:19:23 -05:00
if ( canvas . spaceKey ) return ;
var right _click = evt . button === 2 ;
2010-07-20 07:59:47 -05:00
root _sctm = svgcontent . getScreenCTM ( ) . inverse ( ) ;
var pt = transformPoint ( evt . pageX , evt . pageY , root _sctm ) ,
mouse _x = pt . x * current _zoom ,
mouse _y = pt . y * current _zoom ;
2010-09-10 15:19:23 -05:00
if ( $ . browser . msie ) {
var off = $ ( container . parentNode ) . offset ( ) ;
off _x = svgcontent . getAttribute ( 'x' ) - 0 + off . left - container . parentNode . scrollLeft ;
off _y = svgcontent . getAttribute ( 'y' ) - 0 + off . top - container . parentNode . scrollTop ;
mouse _x = - ( off _x - evt . pageX ) ;
mouse _y = - ( off _y - evt . pageY ) ;
}
2010-07-20 07:59:47 -05:00
evt . preventDefault ( ) ;
2010-09-10 15:19:23 -05:00
if ( right _click ) {
current _mode = "select" ;
lastClickPoint = pt ;
}
2010-07-20 07:59:47 -05:00
// This would seem to be unnecessary...
// if($.inArray(current_mode, ['select', 'resize']) == -1) {
// setGradient();
// }
var x = mouse _x / current _zoom ,
y = mouse _y / current _zoom ,
mouse _target = getMouseTarget ( evt ) ;
2010-09-10 15:19:23 -05:00
// real_x/y ignores grid-snap value
var real _x = r _start _x = start _x = x ;
var real _y = r _start _y = start _y = y ;
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
y = Utils . snapToGrid ( y ) ;
start _x = Utils . snapToGrid ( start _x ) ;
start _y = Utils . snapToGrid ( start _y ) ;
}
2010-07-20 07:59:47 -05:00
// if it is a selector grip, then it must be a single element selected,
// set the mouse_target to that and update the mode to rotate/resize
2010-09-10 15:19:23 -05:00
2010-07-20 07:59:47 -05:00
if ( mouse _target == selectorManager . selectorParentGroup && selectedElements [ 0 ] != null ) {
2010-09-10 15:19:23 -05:00
var grip = evt . target ;
var griptype = elData ( grip , "type" ) ;
2010-07-20 07:59:47 -05:00
// rotating
2010-09-10 15:19:23 -05:00
if ( griptype == "rotate" ) {
2010-07-20 07:59:47 -05:00
current _mode = "rotate" ;
}
// resizing
2010-09-10 15:19:23 -05:00
else if ( griptype == "resize" ) {
2010-07-20 07:59:47 -05:00
current _mode = "resize" ;
2010-09-10 15:19:23 -05:00
current _resize _mode = elData ( grip , "dir" ) ;
2010-07-20 07:59:47 -05:00
}
mouse _target = selectedElements [ 0 ] ;
}
start _transform = mouse _target . getAttribute ( "transform" ) ;
var tlist = getTransformList ( mouse _target ) ;
switch ( current _mode ) {
case "select" :
started = true ;
current _resize _mode = "none" ;
2010-09-10 15:19:23 -05:00
if ( right _click ) started = false ;
2010-07-20 07:59:47 -05:00
if ( mouse _target != svgroot ) {
// if this element is not yet selected, clear selection and select it
if ( selectedElements . indexOf ( mouse _target ) == - 1 ) {
// only clear selection if shift is not pressed (otherwise, add
// element to selection)
if ( ! evt . shiftKey ) {
// No need to do the call here as it will be done on addToSelection
clearSelection ( true ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
addToSelection ( [ mouse _target ] ) ;
justSelected = mouse _target ;
pathActions . clear ( ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// else if it's a path, go into pathedit mode in mouseup
2010-09-10 15:19:23 -05:00
if ( ! right _click ) {
// insert a dummy transform so if the element(s) are moved it will have
// a transform to use for its translate
for ( var i = 0 ; i < selectedElements . length ; ++ i ) {
if ( selectedElements [ i ] == null ) continue ;
var slist = getTransformList ( selectedElements [ i ] ) ;
slist . insertItemBefore ( svgroot . createSVGTransform ( ) , 0 ) ;
}
2010-07-20 07:59:47 -05:00
}
}
2010-09-10 15:19:23 -05:00
else if ( ! right _click ) {
2010-07-20 07:59:47 -05:00
clearSelection ( ) ;
current _mode = "multiselect" ;
if ( rubberBox == null ) {
rubberBox = selectorManager . getRubberBandBox ( ) ;
}
2010-09-10 15:19:23 -05:00
r _start _x *= current _zoom ;
r _start _y *= current _zoom ;
// console.log('p',[evt.pageX, evt.pageY]);
// console.log('c',[evt.clientX, evt.clientY]);
// console.log('o',[evt.offsetX, evt.offsetY]);
// console.log('s',[start_x, start_y]);
2010-02-05 21:36:35 -06:00
assignAttributes ( rubberBox , {
2010-09-10 15:19:23 -05:00
'x' : r _start _x ,
'y' : r _start _y ,
2010-07-20 07:59:47 -05:00
'width' : 0 ,
'height' : 0 ,
'display' : 'inline'
} , 100 ) ;
}
break ;
case "zoom" :
started = true ;
if ( rubberBox == null ) {
rubberBox = selectorManager . getRubberBandBox ( ) ;
}
assignAttributes ( rubberBox , {
2010-09-10 15:19:23 -05:00
'x' : real _x * current _zoom ,
'y' : real _x * current _zoom ,
2010-07-20 07:59:47 -05:00
'width' : 0 ,
'height' : 0 ,
'display' : 'inline'
} , 100 ) ;
break ;
case "resize" :
started = true ;
start _x = x ;
start _y = y ;
// Getting the BBox from the selection box, since we know we
// want to orient around it
init _bbox = getBBox ( $ ( '#selectedBox0' ) [ 0 ] ) ;
$ . each ( init _bbox , function ( key , val ) {
init _bbox [ key ] = val / current _zoom ;
} ) ;
// append three dummy transforms to the tlist so that
// we can translate,scale,translate in mousemove
var pos = getRotationAngle ( mouse _target ) ? 1 : 0 ;
if ( hasMatrixTransform ( tlist ) ) {
tlist . insertItemBefore ( svgroot . createSVGTransform ( ) , pos ) ;
tlist . insertItemBefore ( svgroot . createSVGTransform ( ) , pos ) ;
tlist . insertItemBefore ( svgroot . createSVGTransform ( ) , pos ) ;
} else {
tlist . appendItem ( svgroot . createSVGTransform ( ) ) ;
tlist . appendItem ( svgroot . createSVGTransform ( ) ) ;
tlist . appendItem ( svgroot . createSVGTransform ( ) ) ;
}
break ;
case "fhellipse" :
case "fhrect" :
case "fhpath" :
started = true ;
start _x = x ;
start _y = y ;
d _attr = x + "," + y + " " ;
var stroke _w = cur _shape . stroke _width == 0 ? 1 : cur _shape . stroke _width ;
addSvgElementFromJson ( {
"element" : "polyline" ,
"curStyles" : true ,
"attr" : {
"points" : d _attr ,
"id" : getNextId ( ) ,
"fill" : "none" ,
"opacity" : cur _shape . opacity / 2 ,
"stroke-linecap" : "round" ,
"style" : "pointer-events:none"
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
freehand . minx = x ;
freehand . maxx = x ;
freehand . miny = y ;
freehand . maxy = y ;
break ;
case "image" :
started = true ;
start _x = x ;
start _y = y ;
var newImage = addSvgElementFromJson ( {
"element" : "image" ,
"attr" : {
"x" : x ,
"y" : y ,
"width" : 0 ,
"height" : 0 ,
"id" : getNextId ( ) ,
"opacity" : cur _shape . opacity / 2 ,
"style" : "pointer-events:inherit"
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
2010-09-10 15:19:23 -05:00
setHref ( newImage , last _good _img _url ) ;
2010-07-20 07:59:47 -05:00
preventClickDefault ( newImage ) ;
break ;
case "square" :
// FIXME: once we create the rect, we lose information that this was a square
// (for resizing purposes this could be important)
case "rect" :
started = true ;
start _x = x ;
start _y = y ;
addSvgElementFromJson ( {
"element" : "rect" ,
"curStyles" : true ,
"attr" : {
"x" : x ,
"y" : y ,
"width" : 0 ,
"height" : 0 ,
"id" : getNextId ( ) ,
"opacity" : cur _shape . opacity / 2
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
break ;
case "line" :
started = true ;
var stroke _w = cur _shape . stroke _width == 0 ? 1 : cur _shape . stroke _width ;
addSvgElementFromJson ( {
"element" : "line" ,
"curStyles" : true ,
"attr" : {
"x1" : x ,
"y1" : y ,
"x2" : x ,
"y2" : y ,
"id" : getNextId ( ) ,
"stroke" : cur _shape . stroke ,
"stroke-width" : stroke _w ,
"stroke-dasharray" : cur _shape . stroke _dasharray ,
"stroke-linejoin" : cur _shape . stroke _linejoin ,
"stroke-linecap" : cur _shape . stroke _linecap ,
"stroke-opacity" : cur _shape . stroke _opacity ,
"fill" : "none" ,
"opacity" : cur _shape . opacity / 2 ,
"style" : "pointer-events:none"
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
break ;
case "circle" :
started = true ;
addSvgElementFromJson ( {
"element" : "circle" ,
"curStyles" : true ,
"attr" : {
"cx" : x ,
"cy" : y ,
"r" : 0 ,
"id" : getNextId ( ) ,
"opacity" : cur _shape . opacity / 2
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
break ;
case "ellipse" :
started = true ;
addSvgElementFromJson ( {
"element" : "ellipse" ,
"curStyles" : true ,
"attr" : {
"cx" : x ,
"cy" : y ,
"rx" : 0 ,
"ry" : 0 ,
"id" : getNextId ( ) ,
"opacity" : cur _shape . opacity / 2
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
break ;
case "text" :
started = true ;
var newText = addSvgElementFromJson ( {
"element" : "text" ,
"curStyles" : true ,
"attr" : {
"x" : x ,
"y" : y ,
"id" : getNextId ( ) ,
"fill" : cur _text . fill ,
"stroke-width" : cur _text . stroke _width ,
"font-size" : cur _text . font _size ,
"font-family" : cur _text . font _family ,
"text-anchor" : "middle" ,
"xml:space" : "preserve"
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
// newText.textContent = "text";
break ;
case "path" :
// Fall through
case "pathedit" :
start _x *= current _zoom ;
start _y *= current _zoom ;
pathActions . mouseDown ( evt , mouse _target , start _x , start _y ) ;
started = true ;
break ;
case "textedit" :
start _x *= current _zoom ;
start _y *= current _zoom ;
textActions . mouseDown ( evt , mouse _target , start _x , start _y ) ;
started = true ;
break ;
case "rotate" :
started = true ;
// we are starting an undoable change (a drag-rotation)
canvas . beginUndoableChange ( "transform" , selectedElements ) ;
break ;
default :
// This could occur in an extension
break ;
}
var ext _result = runExtensions ( "mouseDown" , {
event : evt ,
start _x : start _x ,
start _y : start _y ,
selectedElements : selectedElements
} , true ) ;
$ . each ( ext _result , function ( i , r ) {
if ( r && r . started ) {
started = true ;
}
} ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// in this function we do not record any state changes yet (but we do update
// any elements that are still being created, moved or resized on the canvas)
// TODO: svgcanvas should just retain a reference to the image being dragged instead
// of the getId() and getElementById() funkiness - this will help us customize the ids
// a little bit for squares and paths
var mouseMove = function ( evt )
{
if ( ! started ) return ;
if ( evt . button === 1 || canvas . spaceKey ) return ;
var selected = selectedElements [ 0 ] ,
pt = transformPoint ( evt . pageX , evt . pageY , root _sctm ) ,
mouse _x = pt . x * current _zoom ,
mouse _y = pt . y * current _zoom ,
shape = getElem ( getId ( ) ) ;
2010-09-10 15:19:23 -05:00
// IE9 gives the wrong root_sctm
// TODO: Use non-browser sniffing way to make this work
if ( $ . browser . msie ) {
mouse _x = - ( off _x - evt . pageX ) ;
mouse _y = - ( off _y - evt . pageY ) ;
}
var real _x = x = mouse _x / current _zoom ;
var real _y = y = mouse _y / current _zoom ;
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
y = Utils . snapToGrid ( y ) ;
}
2010-07-20 07:59:47 -05:00
evt . preventDefault ( ) ;
switch ( current _mode )
{
case "select" :
// we temporarily use a translate on the element(s) being dragged
// this transform is removed upon mousing up and the element is
// relocated to the new location
if ( selectedElements [ 0 ] != null ) {
var dx = x - start _x ;
var dy = y - start _y ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
dx = Utils . snapToGrid ( dx ) ;
dy = Utils . snapToGrid ( dy ) ;
}
2010-07-20 07:59:47 -05:00
if ( evt . shiftKey ) { var xya = Utils . snapToAngle ( start _x , start _y , x , y ) ; x = xya . x ; y = xya . y ; }
if ( dx != 0 || dy != 0 ) {
var len = selectedElements . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var selected = selectedElements [ i ] ;
if ( selected == null ) break ;
if ( i == 0 ) {
var box = getBBox ( selected ) ;
// selectedBBoxes[i].x = box.x + dx;
// selectedBBoxes[i].y = box.y + dy;
}
// update the dummy transform in our transform list
// to be a translate
var xform = svgroot . createSVGTransform ( ) ;
var tlist = getTransformList ( selected ) ;
2010-08-13 16:14:19 -05:00
// Note that if Webkit and there's no ID for this
// element, the dummy transform may have gotten lost.
// This results in unexpected behaviour
2010-07-20 07:59:47 -05:00
xform . setTranslate ( dx , dy ) ;
if ( tlist . numberOfItems ) {
tlist . replaceItem ( xform , 0 ) ;
} else {
tlist . appendItem ( xform ) ;
}
// update our internal bbox that we're tracking while dragging
selectorManager . requestSelector ( selected ) . resize ( ) ;
}
}
}
break ;
case "multiselect" :
2010-09-10 15:19:23 -05:00
real _x *= current _zoom ;
real _y *= current _zoom ;
2010-07-20 07:59:47 -05:00
assignAttributes ( rubberBox , {
2010-09-10 15:19:23 -05:00
'x' : Math . min ( r _start _x , real _x ) ,
'y' : Math . min ( r _start _y , real _y ) ,
'width' : Math . abs ( real _x - r _start _x ) ,
'height' : Math . abs ( real _y - r _start _y )
2010-07-20 07:59:47 -05:00
} , 100 ) ;
// for each selected:
// - if newList contains selected, do nothing
// - if newList doesn't contain selected, remove it from selected
// - for any newList that was not in selectedElements, add it to selected
var elemsToRemove = [ ] , elemsToAdd = [ ] ,
newList = getIntersectionList ( ) ,
len = selectedElements . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var ind = newList . indexOf ( selectedElements [ i ] ) ;
if ( ind == - 1 ) {
elemsToRemove . push ( selectedElements [ i ] ) ;
}
else {
newList [ ind ] = null ;
}
}
len = newList . length ;
for ( i = 0 ; i < len ; ++ i ) { if ( newList [ i ] ) elemsToAdd . push ( newList [ i ] ) ; }
if ( elemsToRemove . length > 0 )
canvas . removeFromSelection ( elemsToRemove ) ;
if ( elemsToAdd . length > 0 )
addToSelection ( elemsToAdd ) ;
break ;
case "resize" :
// we track the resize bounding box and translate/scale the selected element
// while the mouse is down, when mouse goes up, we use this to recalculate
// the shape's coordinates
var tlist = getTransformList ( selected ) ,
hasMatrix = hasMatrixTransform ( tlist ) ,
box = hasMatrix ? init _bbox : getBBox ( selected ) ,
left = box . x , top = box . y , width = box . width ,
height = box . height , dx = ( x - start _x ) , dy = ( y - start _y ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
dx = Utils . snapToGrid ( dx ) ;
dy = Utils . snapToGrid ( dy ) ;
height = Utils . snapToGrid ( height ) ;
width = Utils . snapToGrid ( width ) ;
}
2010-07-20 07:59:47 -05:00
// if rotated, adjust the dx,dy values
var angle = getRotationAngle ( selected ) ;
if ( angle ) {
var r = Math . sqrt ( dx * dx + dy * dy ) ,
theta = Math . atan2 ( dy , dx ) - angle * Math . PI / 180.0 ;
dx = r * Math . cos ( theta ) ;
dy = r * Math . sin ( theta ) ;
}
// if not stretching in y direction, set dy to 0
// if not stretching in x direction, set dx to 0
if ( current _resize _mode . indexOf ( "n" ) == - 1 && current _resize _mode . indexOf ( "s" ) == - 1 ) {
dy = 0 ;
}
if ( current _resize _mode . indexOf ( "e" ) == - 1 && current _resize _mode . indexOf ( "w" ) == - 1 ) {
dx = 0 ;
}
var ts = null ,
tx = 0 , ty = 0 ,
sy = height ? ( height + dy ) / height : 1 ,
sx = width ? ( width + dx ) / width : 1 ;
// if we are dragging on the north side, then adjust the scale factor and ty
if ( current _resize _mode . indexOf ( "n" ) != - 1 ) {
sy = height ? ( height - dy ) / height : 1 ;
ty = height ;
}
// if we dragging on the east side, then adjust the scale factor and tx
if ( current _resize _mode . indexOf ( "w" ) != - 1 ) {
sx = width ? ( width - dx ) / width : 1 ;
tx = width ;
}
// update the transform list with translate,scale,translate
var translateOrigin = svgroot . createSVGTransform ( ) ,
scale = svgroot . createSVGTransform ( ) ,
translateBack = svgroot . createSVGTransform ( ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
left = Utils . snapToGrid ( left ) ;
tx = Utils . snapToGrid ( tx ) ;
top = Utils . snapToGrid ( top ) ;
ty = Utils . snapToGrid ( ty ) ;
}
2010-07-20 07:59:47 -05:00
translateOrigin . setTranslate ( - ( left + tx ) , - ( top + ty ) ) ;
if ( evt . shiftKey ) {
if ( sx == 1 ) sx = sy
else sy = sx ;
}
scale . setScale ( sx , sy ) ;
translateBack . setTranslate ( left + tx , top + ty ) ;
if ( hasMatrix ) {
var diff = angle ? 1 : 0 ;
tlist . replaceItem ( translateOrigin , 2 + diff ) ;
tlist . replaceItem ( scale , 1 + diff ) ;
tlist . replaceItem ( translateBack , 0 + diff ) ;
} else {
var N = tlist . numberOfItems ;
tlist . replaceItem ( translateBack , N - 3 ) ;
tlist . replaceItem ( scale , N - 2 ) ;
tlist . replaceItem ( translateOrigin , N - 1 ) ;
}
var selectedBBox = selectedBBoxes [ 0 ] ;
if ( selectedBBox ) {
2010-02-05 21:36:35 -06:00
// reset selected bbox top-left position
selectedBBox . x = left ;
selectedBBox . y = top ;
// if this is a translate, adjust the box position
if ( tx ) {
selectedBBox . x += dx ;
}
if ( ty ) {
selectedBBox . y += dy ;
}
2010-07-20 07:59:47 -05:00
}
selectorManager . requestSelector ( selected ) . resize ( ) ;
break ;
case "zoom" :
2010-09-10 15:19:23 -05:00
real _x *= current _zoom ;
real _y *= current _zoom ;
2010-07-20 07:59:47 -05:00
assignAttributes ( rubberBox , {
2010-09-10 15:19:23 -05:00
'x' : Math . min ( r _start _x * current _zoom , real _x ) ,
'y' : Math . min ( r _start _y * current _zoom , real _y ) ,
'width' : Math . abs ( real _x - r _start _x * current _zoom ) ,
'height' : Math . abs ( real _y - r _start _y * current _zoom )
2010-07-20 07:59:47 -05:00
} , 100 ) ;
break ;
case "text" :
assignAttributes ( shape , {
'x' : x ,
'y' : y
} , 1000 ) ;
break ;
case "line" :
// Opera has a problem with suspendRedraw() apparently
var handle = null ;
if ( ! window . opera ) svgroot . suspendRedraw ( 1000 ) ;
2010-04-28 00:22:49 -05:00
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
y = Utils . snapToGrid ( y ) ;
}
2010-07-20 07:59:47 -05:00
var x2 = x ;
var y2 = y ;
2010-04-28 00:22:49 -05:00
2010-07-20 07:59:47 -05:00
if ( evt . shiftKey ) { var xya = Utils . snapToAngle ( start _x , start _y , x2 , y2 ) ; x2 = xya . x ; y2 = xya . y ; }
shape . setAttributeNS ( null , "x2" , x2 ) ;
shape . setAttributeNS ( null , "y2" , y2 ) ;
if ( ! window . opera ) svgroot . unsuspendRedraw ( handle ) ;
break ;
case "foreignObject" :
// fall through
case "square" :
// fall through
case "rect" :
// fall through
case "image" :
var square = ( current _mode == 'square' ) || evt . shiftKey ,
w = Math . abs ( x - start _x ) ,
h = Math . abs ( y - start _y ) ,
new _x , new _y ;
if ( square ) {
w = h = Math . max ( w , h ) ;
new _x = start _x < x ? start _x : start _x - w ;
new _y = start _y < y ? start _y : start _y - h ;
} else {
new _x = Math . min ( start _x , x ) ;
new _y = Math . min ( start _y , y ) ;
}
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
w = Utils . snapToGrid ( w ) ;
h = Utils . snapToGrid ( h ) ;
new _x = Utils . snapToGrid ( new _x ) ;
new _y = Utils . snapToGrid ( new _y ) ;
}
2010-07-20 07:59:47 -05:00
assignAttributes ( shape , {
'width' : w ,
'height' : h ,
'x' : new _x ,
'y' : new _y
} , 1000 ) ;
break ;
case "circle" :
var c = $ ( shape ) . attr ( [ "cx" , "cy" ] ) ;
var cx = c . cx , cy = c . cy ,
rad = Math . sqrt ( ( x - cx ) * ( x - cx ) + ( y - cy ) * ( y - cy ) ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
rad = Utils . snapToGrid ( rad ) ;
}
2010-07-20 07:59:47 -05:00
shape . setAttributeNS ( null , "r" , rad ) ;
break ;
case "ellipse" :
var c = $ ( shape ) . attr ( [ "cx" , "cy" ] ) ;
var cx = c . cx , cy = c . cy ;
// Opera has a problem with suspendRedraw() apparently
handle = null ;
if ( ! window . opera ) svgroot . suspendRedraw ( 1000 ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
cx = Utils . snapToGrid ( cx ) ;
y = Utils . snapToGrid ( y ) ;
cy = Utils . snapToGrid ( cy ) ;
}
2010-07-20 07:59:47 -05:00
shape . setAttributeNS ( null , "rx" , Math . abs ( x - cx ) ) ;
var ry = Math . abs ( evt . shiftKey ? ( x - cx ) : ( y - cy ) ) ;
shape . setAttributeNS ( null , "ry" , ry ) ;
if ( ! window . opera ) svgroot . unsuspendRedraw ( handle ) ;
break ;
case "fhellipse" :
case "fhrect" :
freehand . minx = Math . min ( x , freehand . minx ) ;
freehand . maxx = Math . max ( x , freehand . maxx ) ;
freehand . miny = Math . min ( y , freehand . miny ) ;
freehand . maxy = Math . max ( y , freehand . maxy ) ;
// break; missing on purpose
case "fhpath" :
start _x = x ;
start _y = y ;
d _attr += + x + "," + y + " " ;
shape . setAttributeNS ( null , "points" , d _attr ) ;
break ;
// update path stretch line coordinates
case "path" :
// fall through
case "pathedit" :
x *= current _zoom ;
y *= current _zoom ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
y = Utils . snapToGrid ( y ) ;
start _x = Utils . snapToGrid ( start _x ) ;
start _y = Utils . snapToGrid ( start _y ) ;
}
2010-07-20 07:59:47 -05:00
if ( evt . shiftKey ) {
var x1 = path . dragging ? path . dragging [ 0 ] : start _x ;
var y1 = path . dragging ? path . dragging [ 1 ] : start _y ;
var xya = Utils . snapToAngle ( x1 , y1 , x , y ) ;
x = xya . x ; y = xya . y ;
}
if ( rubberBox && rubberBox . getAttribute ( 'display' ) != 'none' ) {
assignAttributes ( rubberBox , {
2010-09-10 15:19:23 -05:00
'x' : Math . min ( r _start _x , real _x ) ,
'y' : Math . min ( r _start _y , real _y ) ,
'width' : Math . abs ( real _x - r _start _x ) ,
'height' : Math . abs ( real _y - r _start _y )
2010-07-20 07:59:47 -05:00
} , 100 ) ;
}
pathActions . mouseMove ( x , y ) ;
break ;
case "textedit" :
x *= current _zoom ;
y *= current _zoom ;
2010-04-18 12:55:02 -05:00
// if(rubberBox && rubberBox.getAttribute('display') != 'none') {
// assignAttributes(rubberBox, {
// 'x': Math.min(start_x,x),
// 'y': Math.min(start_y,y),
// 'width': Math.abs(x-start_x),
// 'height': Math.abs(y-start_y)
// },100);
// }
2010-07-20 07:59:47 -05:00
textActions . mouseMove ( mouse _x , mouse _y ) ;
break ;
case "rotate" :
var box = getBBox ( selected ) ,
cx = box . x + box . width / 2 ,
cy = box . y + box . height / 2 ,
m = getMatrix ( selected ) ,
center = transformPoint ( cx , cy , m ) ;
cx = center . x ;
cy = center . y ;
var angle = ( ( Math . atan2 ( cy - y , cx - x ) * ( 180 / Math . PI ) ) - 90 ) % 360 ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
angle = Utils . snapToGrid ( angle ) ;
}
2010-07-20 07:59:47 -05:00
if ( evt . shiftKey ) { // restrict rotations to nice angles (WRS)
var snap = 45 ;
angle = Math . round ( angle / snap ) * snap ;
}
canvas . setRotationAngle ( angle < - 180 ? ( 360 + angle ) : angle , true ) ;
call ( "changed" , selectedElements ) ;
break ;
default :
break ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
runExtensions ( "mouseMove" , {
event : evt ,
mouse _x : mouse _x ,
mouse _y : mouse _y ,
selected : selected
} ) ;
} ; // mouseMove()
// - in create mode, the element's opacity is set properly, we create an InsertElementCommand
// and store it on the Undo stack
// - in move/resize mode, the element's attributes which were affected by the move/resize are
// identified, a ChangeElementCommand is created and stored on the stack for those attrs
// this is done in when we recalculate the selected dimensions()
var mouseUp = function ( evt )
{
2010-09-10 15:19:23 -05:00
if ( evt . button === 2 ) return ;
2010-07-20 07:59:47 -05:00
var tempJustSelected = justSelected ;
justSelected = null ;
if ( ! started ) return ;
var pt = transformPoint ( evt . pageX , evt . pageY , root _sctm ) ,
mouse _x = pt . x * current _zoom ,
mouse _y = pt . y * current _zoom ,
x = mouse _x / current _zoom ,
y = mouse _y / current _zoom ,
element = getElem ( getId ( ) ) ,
keep = false ;
2010-09-10 15:19:23 -05:00
var real _x = x ;
var real _y = y ;
2010-07-20 07:59:47 -05:00
started = false ;
switch ( current _mode )
{
// intentionally fall-through to select here
case "resize" :
case "multiselect" :
if ( rubberBox != null ) {
rubberBox . setAttribute ( "display" , "none" ) ;
curBBoxes = [ ] ;
}
current _mode = "select" ;
case "select" :
if ( selectedElements [ 0 ] != null ) {
// if we only have one selected element
if ( selectedElements [ 1 ] == null ) {
// set our current stroke/fill properties to the element's
var selected = selectedElements [ 0 ] ;
if ( selected . tagName != "g" && selected . tagName != "image" && selected . tagName != "foreignObject" ) {
cur _properties . fill = selected . getAttribute ( "fill" ) ;
cur _properties . fill _opacity = selected . getAttribute ( "fill-opacity" ) ;
cur _properties . stroke = selected . getAttribute ( "stroke" ) ;
cur _properties . stroke _opacity = selected . getAttribute ( "stroke-opacity" ) ;
cur _properties . stroke _width = selected . getAttribute ( "stroke-width" ) ;
cur _properties . stroke _dasharray = selected . getAttribute ( "stroke-dasharray" ) ;
cur _properties . stroke _linejoin = selected . getAttribute ( "stroke-linejoin" ) ;
cur _properties . stroke _linecap = selected . getAttribute ( "stroke-linecap" ) ;
}
if ( selected . tagName == "text" ) {
cur _text . font _size = selected . getAttribute ( "font-size" ) ;
cur _text . font _family = selected . getAttribute ( "font-family" ) ;
}
selectorManager . requestSelector ( selected ) . showGrips ( true ) ;
// This shouldn't be necessary as it was done on mouseDown...
// call("selected", [selected]);
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// always recalculate dimensions to strip off stray identity transforms
recalculateAllSelectedDimensions ( ) ;
// if it was being dragged/resized
2010-09-10 15:19:23 -05:00
if ( real _x != r _start _x || real _y != r _start _y ) {
2010-07-20 07:59:47 -05:00
var len = selectedElements . length ;
for ( var i = 0 ; i < len ; ++ i ) {
if ( selectedElements [ i ] == null ) break ;
if ( selectedElements [ i ] . tagName != 'g' ) {
// Not needed for groups (incorrectly resizes elems), possibly not needed at all?
selectorManager . requestSelector ( selectedElements [ i ] ) . resize ( ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// no change in position/size, so maybe we should move to pathedit
else {
var t = evt . target ;
if ( selectedElements [ 0 ] . nodeName == "path" && selectedElements [ 1 ] == null ) {
pathActions . select ( t ) ;
} // if it was a path
else if ( selectedElements [ 0 ] . nodeName == "text" && selectedElements [ 1 ] == null ) {
textActions . select ( t , x , y ) ;
} // if it was a path
// else, if it was selected and this is a shift-click, remove it from selection
else if ( evt . shiftKey ) {
if ( tempJustSelected != t ) {
canvas . removeFromSelection ( [ t ] ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
} // no change in mouse position
}
// we return immediately from select so that the obj_num is not incremented
return ;
break ;
case "zoom" :
if ( rubberBox != null ) {
rubberBox . setAttribute ( "display" , "none" ) ;
}
var factor = evt . shiftKey ? . 5 : 2 ;
call ( "zoomed" , {
2010-09-10 15:19:23 -05:00
'x' : Math . min ( r _start _x , real _x ) ,
'y' : Math . min ( r _start _y , real _y ) ,
'width' : Math . abs ( real _x - r _start _x ) ,
'height' : Math . abs ( real _y - r _start _y ) ,
2010-07-20 07:59:47 -05:00
'factor' : factor
} ) ;
return ;
case "fhpath" :
// Check that the path contains at least 2 points; a degenerate one-point path
// causes problems.
// Webkit ignores how we set the points attribute with commas and uses space
// to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870
var coords = element . getAttribute ( 'points' ) ;
var commaIndex = coords . indexOf ( ',' ) ;
if ( commaIndex >= 0 ) {
keep = coords . indexOf ( ',' , commaIndex + 1 ) >= 0 ;
} else {
keep = coords . indexOf ( ' ' , coords . indexOf ( ' ' ) + 1 ) >= 0 ;
}
if ( keep ) {
element = pathActions . smoothPolylineIntoPath ( element ) ;
}
break ;
case "line" :
var attrs = $ ( element ) . attr ( [ "x1" , "x2" , "y1" , "y2" ] ) ;
keep = ( attrs . x1 != attrs . x2 || attrs . y1 != attrs . y2 ) ;
break ;
case "foreignObject" :
case "square" :
case "rect" :
case "image" :
var attrs = $ ( element ) . attr ( [ "width" , "height" ] ) ;
// Image should be kept regardless of size (use inherit dimensions later)
keep = ( attrs . width != 0 || attrs . height != 0 ) || current _mode === "image" ;
break ;
case "circle" :
keep = ( element . getAttribute ( 'r' ) != 0 ) ;
break ;
case "ellipse" :
var attrs = $ ( element ) . attr ( [ "rx" , "ry" ] ) ;
keep = ( attrs . rx != null || attrs . ry != null ) ;
break ;
case "fhellipse" :
if ( ( freehand . maxx - freehand . minx ) > 0 &&
( freehand . maxy - freehand . miny ) > 0 ) {
element = addSvgElementFromJson ( {
"element" : "ellipse" ,
"curStyles" : true ,
"attr" : {
"cx" : ( freehand . minx + freehand . maxx ) / 2 ,
"cy" : ( freehand . miny + freehand . maxy ) / 2 ,
"rx" : ( freehand . maxx - freehand . minx ) / 2 ,
"ry" : ( freehand . maxy - freehand . miny ) / 2 ,
"id" : getId ( )
}
} ) ;
call ( "changed" , [ element ] ) ;
2010-02-05 21:36:35 -06:00
keep = true ;
2010-07-20 07:59:47 -05:00
}
break ;
case "fhrect" :
if ( ( freehand . maxx - freehand . minx ) > 0 &&
( freehand . maxy - freehand . miny ) > 0 ) {
element = addSvgElementFromJson ( {
"element" : "rect" ,
"curStyles" : true ,
"attr" : {
"x" : freehand . minx ,
"y" : freehand . miny ,
"width" : ( freehand . maxx - freehand . minx ) ,
"height" : ( freehand . maxy - freehand . miny ) ,
"id" : getId ( )
}
} ) ;
call ( "changed" , [ element ] ) ;
2010-02-05 21:36:35 -06:00
keep = true ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
break ;
case "text" :
keep = true ;
addToSelection ( [ element ] ) ;
textActions . start ( element ) ;
break ;
case "path" :
// set element to null here so that it is not removed nor finalized
2010-02-05 21:36:35 -06:00
element = null ;
2010-07-20 07:59:47 -05:00
// continue to be set to true so that mouseMove happens
started = true ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var res = pathActions . mouseUp ( evt , element , mouse _x , mouse _y ) ;
element = res . element
keep = res . keep ;
break ;
case "pathedit" :
keep = true ;
element = null ;
pathActions . mouseUp ( evt ) ;
break ;
case "textedit" :
keep = false ;
element = null ;
textActions . mouseUp ( evt , mouse _x , mouse _y ) ;
break ;
case "rotate" :
keep = true ;
element = null ;
current _mode = "select" ;
var batchCmd = canvas . finishUndoableChange ( ) ;
if ( ! batchCmd . isEmpty ( ) ) {
addCommandToHistory ( batchCmd ) ;
2010-03-10 19:02:40 -06:00
}
2010-07-20 07:59:47 -05:00
// perform recalculation to weed out any stray identity transforms that might get stuck
recalculateAllSelectedDimensions ( ) ;
2010-09-10 15:19:23 -05:00
call ( "changed" , selectedElements ) ;
2010-07-20 07:59:47 -05:00
break ;
default :
// This could occur in an extension
break ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var ext _result = runExtensions ( "mouseUp" , {
event : evt ,
mouse _x : mouse _x ,
mouse _y : mouse _y
} , true ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
$ . each ( ext _result , function ( i , r ) {
if ( r ) {
keep = r . keep || keep ;
element = r . element ;
started = r . started || started ;
2010-02-05 21:36:35 -06:00
}
} ) ;
2010-07-20 07:59:47 -05:00
if ( ! keep && element != null ) {
element . parentNode . removeChild ( element ) ;
element = null ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var t = evt . target ;
// if this element is in a group, go up until we reach the top-level group
// just below the layer groups
// TODO: once we implement links, we also would have to check for <a> elements
while ( t . parentNode . parentNode . tagName == "g" ) {
t = t . parentNode ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
// if we are not in the middle of creating a path, and we've clicked on some shape,
// then go to Select mode.
// WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
if ( ( current _mode != "path" || current _path _pts . length == 0 ) &&
t . parentNode . id != "selectorParentGroup" &&
t . id != "svgcanvas" && t . id != "svgroot" )
{
// switch into "select" mode if we've clicked on an element
2010-09-10 15:19:23 -05:00
clearSelection ( true ) ;
2010-07-20 07:59:47 -05:00
addToSelection ( [ t ] , true ) ;
canvas . setMode ( "select" ) ;
}
} else if ( element != null ) {
canvas . addedNew = true ;
var ani _dur = . 2 , c _ani ;
if ( opac _ani . beginElement && element . getAttribute ( 'opacity' ) != cur _shape . opacity ) {
c _ani = $ ( opac _ani ) . clone ( ) . attr ( {
to : cur _shape . opacity ,
dur : ani _dur
} ) . appendTo ( element ) ;
2010-08-13 16:14:19 -05:00
try {
// Fails in FF4 on foreignObject
c _ani [ 0 ] . beginElement ( ) ;
} catch ( e ) { }
2010-07-20 07:59:47 -05:00
} else {
ani _dur = 0 ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
// Ideally this would be done on the endEvent of the animation,
// but that doesn't seem to be supported in Webkit
setTimeout ( function ( ) {
if ( c _ani ) c _ani . remove ( ) ;
element . setAttribute ( "opacity" , cur _shape . opacity ) ;
element . setAttribute ( "style" , "pointer-events:inherit" ) ;
cleanupElement ( element ) ;
if ( current _mode == "path" ) {
pathActions . toEditMode ( element ) ;
} else if ( current _mode == "text" || current _mode == "image" || current _mode == "foreignObject" ) {
// keep us in the tool we were in unless it was a text or image element
addToSelection ( [ element ] , true ) ;
2010-09-10 15:19:23 -05:00
} else {
clearSelection ( true ) ;
addToSelection ( [ element ] , true ) ;
2010-07-20 07:59:47 -05:00
}
// we create the insert command that is stored on the stack
// undo means to call cmd.unapply(), redo means to call cmd.apply()
addCommandToHistory ( new InsertElementCommand ( element ) ) ;
2010-09-10 15:19:23 -05:00
2010-07-20 07:59:47 -05:00
call ( "changed" , [ element ] ) ;
} , ani _dur * 1000 ) ;
}
start _transform = null ;
} ;
// prevent links from being followed in the canvas
var handleLinkInCanvas = function ( e ) {
e . preventDefault ( ) ;
return false ;
} ;
$ ( container ) . mousedown ( mouseDown ) . mousemove ( mouseMove ) . click ( handleLinkInCanvas ) ;
$ ( window ) . mouseup ( mouseUp ) ;
$ ( container ) . bind ( "mousewheel DOMMouseScroll" , function ( e ) {
if ( ! e . shiftKey ) return ;
e . preventDefault ( ) ;
root _sctm = svgcontent . getScreenCTM ( ) . inverse ( ) ;
var pt = transformPoint ( e . pageX , e . pageY , root _sctm ) ;
var bbox = {
'x' : pt . x ,
'y' : pt . y ,
'width' : 0 ,
'height' : 0
} ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
// Respond to mouse wheel in IE/Webkit/Opera.
// (It returns up/dn motion in multiples of 120)
if ( e . wheelDelta ) {
if ( e . wheelDelta >= 120 ) {
bbox . factor = 2 ;
} else if ( e . wheelDelta <= - 120 ) {
bbox . factor = . 5 ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
} else if ( e . detail ) {
if ( e . detail > 0 ) {
bbox . factor = . 5 ;
} else if ( e . detail < 0 ) {
bbox . factor = 2 ;
}
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
if ( ! bbox . factor ) return ;
call ( "zoomed" , bbox ) ;
} ) ;
} ( ) ) ;
// Function: preventClickDefault
// Prevents default browser click behaviour on the given element
//
// Parameters:
// img - The DOM element to prevent the cilck on
var preventClickDefault = function ( img ) {
$ ( img ) . click ( function ( e ) { e . preventDefault ( ) } ) ;
}
// Group: Text edit functions
// Functions relating to editing text elements
var textActions = canvas . textActions = function ( ) {
var curtext , current _text ;
var textinput ;
var cursor ;
var selblock ;
var blinker ;
var chardata = [ ] ;
var textbb , transbb ;
var matrix ;
var last _x , last _y ;
var allow _dbl ;
function setCursor ( index ) {
var empty = ( textinput . value === "" ) ;
2010-08-13 16:14:19 -05:00
$ ( textinput ) . focus ( ) ;
2010-07-20 07:59:47 -05:00
if ( ! arguments . length ) {
if ( empty ) {
index = 0 ;
} else {
if ( textinput . selectionEnd !== textinput . selectionStart ) return ;
index = textinput . selectionEnd ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
}
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var charbb ;
charbb = chardata [ index ] ;
if ( ! empty ) {
textinput . setSelectionRange ( index , index ) ;
}
cursor = getElem ( "text_cursor" ) ;
if ( ! cursor ) {
cursor = document . createElementNS ( svgns , "line" ) ;
assignAttributes ( cursor , {
'id' : "text_cursor" ,
'stroke' : "#333" ,
'stroke-width' : 1
} ) ;
cursor = getElem ( "selectorParentGroup" ) . appendChild ( cursor ) ;
}
if ( ! blinker ) {
blinker = setInterval ( function ( ) {
var show = ( cursor . getAttribute ( 'display' ) === 'none' ) ;
cursor . setAttribute ( 'display' , show ? 'inline' : 'none' ) ;
} , 600 ) ;
2010-05-11 00:38:21 -05:00
2010-07-20 07:59:47 -05:00
}
var start _pt = ptToScreen ( charbb . x , textbb . y ) ;
var end _pt = ptToScreen ( charbb . x , ( textbb . y + textbb . height ) ) ;
assignAttributes ( cursor , {
x1 : start _pt . x ,
y1 : start _pt . y ,
x2 : end _pt . x ,
y2 : end _pt . y ,
visibility : 'visible' ,
display : 'inline'
} ) ;
if ( selblock ) selblock . setAttribute ( 'd' , '' ) ;
}
function setSelection ( start , end , skipInput ) {
if ( start === end ) {
setCursor ( end ) ;
return ;
}
if ( ! skipInput ) {
textinput . setSelectionRange ( start , end ) ;
}
selblock = getElem ( "text_selectblock" ) ;
if ( ! selblock ) {
2010-05-11 00:38:21 -05:00
2010-07-20 07:59:47 -05:00
selblock = document . createElementNS ( svgns , "path" ) ;
2010-04-18 12:55:02 -05:00
assignAttributes ( selblock , {
2010-07-20 07:59:47 -05:00
'id' : "text_selectblock" ,
'fill' : "green" ,
'opacity' : . 5 ,
'style' : "pointer-events:none"
2010-04-18 12:55:02 -05:00
} ) ;
2010-07-20 07:59:47 -05:00
getElem ( "selectorParentGroup" ) . appendChild ( selblock ) ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
var startbb = chardata [ start ] ;
var endbb = chardata [ end ] ;
cursor . setAttribute ( 'visibility' , 'hidden' ) ;
var tl = ptToScreen ( startbb . x , textbb . y ) ,
tr = ptToScreen ( startbb . x + ( endbb . x - startbb . x ) , textbb . y ) ,
bl = ptToScreen ( startbb . x , textbb . y + textbb . height ) ,
br = ptToScreen ( startbb . x + ( endbb . x - startbb . x ) , textbb . y + textbb . height ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var dstr = "M" + tl . x + "," + tl . y
+ " L" + tr . x + "," + tr . y
+ " " + br . x + "," + br . y
+ " " + bl . x + "," + bl . y + "z" ;
assignAttributes ( selblock , {
d : dstr ,
'display' : 'inline'
} ) ;
}
function getIndexFromPoint ( mouse _x , mouse _y ) {
// Position cursor here
var pt = svgroot . createSVGPoint ( ) ;
pt . x = mouse _x ;
pt . y = mouse _y ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
// No content, so return 0
if ( chardata . length == 1 ) return 0 ;
// Determine if cursor should be on left or right of character
var charpos = curtext . getCharNumAtPosition ( pt ) ;
if ( charpos < 0 ) {
// Out of text range, look at mouse coords
charpos = chardata . length - 2 ;
if ( mouse _x <= chardata [ 0 ] . x ) {
charpos = 0 ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
} else if ( charpos >= chardata . length - 2 ) {
charpos = chardata . length - 2 ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
var charbb = chardata [ charpos ] ;
var mid = charbb . x + ( charbb . width / 2 ) ;
if ( mouse _x > mid ) {
charpos ++ ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
return charpos ;
}
function setCursorFromPoint ( mouse _x , mouse _y ) {
setCursor ( getIndexFromPoint ( mouse _x , mouse _y ) ) ;
}
function setEndSelectionFromPoint ( x , y , apply ) {
var i1 = textinput . selectionStart ;
var i2 = getIndexFromPoint ( x , y ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var start = Math . min ( i1 , i2 ) ;
var end = Math . max ( i1 , i2 ) ;
setSelection ( start , end , ! apply ) ;
}
function screenToPt ( x _in , y _in ) {
var out = {
x : x _in ,
y : y _in
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
out . x /= current _zoom ;
out . y /= current _zoom ;
2010-05-11 00:38:21 -05:00
2010-07-20 07:59:47 -05:00
if ( matrix ) {
var pt = transformPoint ( out . x , out . y , matrix . inverse ( ) ) ;
out . x = pt . x ;
out . y = pt . y ;
}
2010-05-11 00:38:21 -05:00
2010-07-20 07:59:47 -05:00
return out ;
}
function ptToScreen ( x _in , y _in ) {
var out = {
x : x _in ,
y : y _in
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
if ( matrix ) {
var pt = transformPoint ( out . x , out . y , matrix ) ;
out . x = pt . x ;
out . y = pt . y ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
out . x *= current _zoom ;
out . y *= current _zoom ;
return out ;
}
function hideCursor ( ) {
if ( cursor ) {
cursor . setAttribute ( 'visibility' , 'hidden' ) ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
}
function selectAll ( evt ) {
setSelection ( 0 , curtext . textContent . length ) ;
$ ( this ) . unbind ( evt ) ;
}
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
function selectWord ( evt ) {
if ( ! allow _dbl ) return ;
var ept = transformPoint ( evt . pageX , evt . pageY , root _sctm ) ,
mouse _x = ept . x * current _zoom ,
mouse _y = ept . y * current _zoom ;
var pt = screenToPt ( mouse _x , mouse _y ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var index = getIndexFromPoint ( pt . x , pt . y ) ;
var str = curtext . textContent ;
var first = str . substr ( 0 , index ) . replace ( /[a-z0-9]+$/i , '' ) . length ;
var m = str . substr ( index ) . match ( /^[a-z0-9]+/i ) ;
var last = ( m ? m [ 0 ] . length : 0 ) + index ;
setSelection ( first , last ) ;
// Set tripleclick
$ ( evt . target ) . click ( selectAll ) ;
setTimeout ( function ( ) {
$ ( evt . target ) . unbind ( 'click' , selectAll ) ;
} , 300 ) ;
}
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
return {
select : function ( target , x , y ) {
if ( current _text == target ) {
curtext = target ;
textActions . toEditMode ( x , y ) ;
} // going into pathedit mode
else {
current _text = target ;
}
} ,
start : function ( elem ) {
curtext = elem ;
textActions . toEditMode ( ) ;
} ,
mouseDown : function ( evt , mouse _target , start _x , start _y ) {
var pt = screenToPt ( start _x , start _y ) ;
textinput . focus ( ) ;
setCursorFromPoint ( pt . x , pt . y ) ;
last _x = start _x ;
last _y = start _y ;
// TODO: Find way to block native selection
} ,
mouseMove : function ( mouse _x , mouse _y ) {
var pt = screenToPt ( mouse _x , mouse _y ) ;
setEndSelectionFromPoint ( pt . x , pt . y ) ;
} ,
mouseUp : function ( evt , mouse _x , mouse _y ) {
var pt = screenToPt ( mouse _x , mouse _y ) ;
setEndSelectionFromPoint ( pt . x , pt . y , true ) ;
// TODO: Find a way to make this work: Use transformed BBox instead of evt.target
2010-04-18 12:55:02 -05:00
// if(last_x === mouse_x && last_y === mouse_y
// && !Utils.rectsIntersect(transbb, {x: pt.x, y: pt.y, width:0, height:0})) {
// textActions.toSelectMode(true);
// }
2010-07-20 07:59:47 -05:00
if ( last _x === mouse _x && last _y === mouse _y && evt . target !== curtext ) {
textActions . toSelectMode ( true ) ;
}
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
} ,
setCursor : setCursor ,
toEditMode : function ( x , y ) {
allow _dbl = false ;
current _mode = "textedit" ;
selectorManager . requestSelector ( curtext ) . showGrips ( false ) ;
2010-08-13 16:14:19 -05:00
// Make selector group accept clicks
var sel = selectorManager . requestSelector ( curtext ) . selectorRect ;
2010-07-20 07:59:47 -05:00
textActions . init ( ) ;
$ ( curtext ) . css ( 'cursor' , 'text' ) ;
2010-05-26 14:37:55 -05:00
// if(support.editableText) {
// curtext.setAttribute('editable', 'simple');
// return;
// }
2010-07-20 07:59:47 -05:00
if ( ! arguments . length ) {
setCursor ( ) ;
} else {
var pt = screenToPt ( x , y ) ;
setCursorFromPoint ( pt . x , pt . y ) ;
}
setTimeout ( function ( ) {
allow _dbl = true ;
} , 300 ) ;
} ,
toSelectMode : function ( selectElem ) {
current _mode = "select" ;
clearInterval ( blinker ) ;
blinker = null ;
if ( selblock ) $ ( selblock ) . attr ( 'display' , 'none' ) ;
if ( cursor ) $ ( cursor ) . attr ( 'visibility' , 'hidden' ) ;
$ ( curtext ) . css ( 'cursor' , 'move' ) ;
if ( selectElem ) {
clearSelection ( ) ;
2010-04-18 12:55:02 -05:00
$ ( curtext ) . css ( 'cursor' , 'move' ) ;
2010-07-20 07:59:47 -05:00
call ( "selected" , [ curtext ] ) ;
addToSelection ( [ curtext ] , true ) ;
}
if ( curtext && ! curtext . textContent . length ) {
// No content, so delete
canvas . deleteSelectedElements ( ) ;
}
$ ( textinput ) . blur ( ) ;
curtext = false ;
2010-05-26 14:37:55 -05:00
// if(support.editableText) {
// curtext.removeAttribute('editable');
// }
2010-07-20 07:59:47 -05:00
} ,
setInputElem : function ( elem ) {
textinput = elem ;
2010-08-13 16:14:19 -05:00
// $(textinput).blur(hideCursor);
2010-07-20 07:59:47 -05:00
} ,
clear : function ( ) {
current _text = null ;
if ( current _mode == "textedit" ) {
textActions . toSelectMode ( ) ;
}
} ,
init : function ( inputElem ) {
if ( ! curtext ) return ;
2010-05-26 14:37:55 -05:00
// if(support.editableText) {
// curtext.select();
// return;
// }
2010-07-20 07:59:47 -05:00
if ( ! curtext . parentNode ) {
// Result of the ffClone, need to get correct element
curtext = selectedElements [ 0 ] ;
selectorManager . requestSelector ( curtext ) . showGrips ( false ) ;
}
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
var str = curtext . textContent ;
var len = str . length ;
var xform = curtext . getAttribute ( 'transform' ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
textbb = getBBox ( curtext ) ;
matrix = xform ? getMatrix ( curtext ) : null ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
chardata = Array ( len ) ;
textinput . focus ( ) ;
$ ( curtext ) . unbind ( 'dblclick' , selectWord ) . dblclick ( selectWord ) ;
if ( ! len ) {
var end = { x : textbb . x + ( textbb . width / 2 ) , width : 0 } ;
}
for ( var i = 0 ; i < len ; i ++ ) {
var start = curtext . getStartPositionOfChar ( i ) ;
var end = curtext . getEndPositionOfChar ( i ) ;
// Get a "bbox" equivalent for each character. Uses the
// bbox data of the actual text for y, height purposes
// TODO: Decide if y, width and height are actually necessary
chardata [ i ] = {
x : start . x ,
y : textbb . y , // start.y?
width : end . x - start . x ,
height : textbb . height
} ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
// Add a last bbox for cursor at end of text
chardata . push ( {
x : end . x ,
width : 0
} ) ;
setSelection ( textinput . selectionStart , textinput . selectionEnd , true ) ;
2010-04-18 12:55:02 -05:00
}
2010-07-20 07:59:47 -05:00
}
} ( ) ;
// Group: Path edit functions
// Functions relating to editing path elements
var pathActions = this . pathActions = function ( ) {
var subpath = false ;
var pathData = { } ;
var current _path ;
var path ;
var segData = {
2 : [ 'x' , 'y' ] ,
4 : [ 'x' , 'y' ] ,
6 : [ 'x' , 'y' , 'x1' , 'y1' , 'x2' , 'y2' ] ,
8 : [ 'x' , 'y' , 'x1' , 'y1' ] ,
10 : [ 'x' , 'y' , 'r1' , 'r2' , 'angle' , 'largeArcFlag' , 'sweepFlag' ] ,
12 : [ 'x' ] ,
2010-08-13 16:14:19 -05:00
14 : [ 'y' ] ,
16 : [ 'x' , 'y' , 'x2' , 'y2' ] ,
18 : [ 'x' , 'y' ]
2010-07-20 07:59:47 -05:00
} ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
function retPath ( ) {
return path ;
}
function resetD ( p ) {
p . setAttribute ( "d" , pathActions . convertPath ( p ) ) ;
}
function insertItemBefore ( elem , newseg , index ) {
// Support insertItemBefore on paths for FF2
var list = elem . pathSegList ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( support . pathInsertItemBefore ) {
list . insertItemBefore ( newseg , index ) ;
return ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
var len = list . numberOfItems ;
var arr = [ ] ;
for ( var i = 0 ; i < len ; i ++ ) {
var cur _seg = list . getItem ( i ) ;
arr . push ( cur _seg )
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
list . clear ( ) ;
for ( var i = 0 ; i < len ; i ++ ) {
if ( i == index ) { //index+1
list . appendItem ( newseg ) ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
list . appendItem ( arr [ i ] ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
// TODO: See if this should just live in replacePathSeg
function ptObjToArr ( type , seg _item ) {
var arr = segData [ type ] , len = arr . length ;
var out = Array ( len ) ;
for ( var i = 0 ; i < len ; i ++ ) {
out [ i ] = seg _item [ arr [ i ] ] ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
return out ;
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
function getGripContainer ( ) {
var c = getElem ( "pathpointgrip_container" ) ;
if ( ! c ) {
var parent = getElem ( "selectorParentGroup" ) ;
c = parent . appendChild ( document . createElementNS ( svgns , "g" ) ) ;
c . id = "pathpointgrip_container" ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
return c ;
}
var addPointGrip = function ( index , x , y ) {
// create the container of all the point grips
var pointGripContainer = getGripContainer ( ) ;
var pointGrip = getElem ( "pathpointgrip_" + index ) ;
// create it
if ( ! pointGrip ) {
pointGrip = document . createElementNS ( svgns , "circle" ) ;
assignAttributes ( pointGrip , {
'id' : "pathpointgrip_" + index ,
'display' : "none" ,
'r' : 4 ,
'fill' : "#0FF" ,
'stroke' : "#00F" ,
'stroke-width' : 2 ,
'cursor' : 'move' ,
'style' : 'pointer-events:all' ,
'xlink:title' : uiStrings . pathNodeTooltip
} ) ;
pointGrip = pointGripContainer . appendChild ( pointGrip ) ;
var grip = $ ( '#pathpointgrip_' + index ) ;
grip . dblclick ( function ( ) {
if ( path ) path . setSegType ( ) ;
} ) ;
}
if ( x && y ) {
// set up the point grip element and display it
assignAttributes ( pointGrip , {
'cx' : x ,
'cy' : y ,
'display' : "inline"
} ) ;
}
return pointGrip ;
} ;
var getPointGrip = function ( seg , update ) {
var index = seg . index ;
var pointGrip = addPointGrip ( index ) ;
if ( update ) {
var pt = getGripPt ( seg ) ;
assignAttributes ( pointGrip , {
'cx' : pt . x ,
'cy' : pt . y ,
'display' : "inline"
} ) ;
}
return pointGrip ;
}
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
var getSegSelector = function ( seg , update ) {
var index = seg . index ;
var segLine = getElem ( "segline_" + index ) ;
if ( ! segLine ) {
2010-02-26 14:51:27 -06:00
var pointGripContainer = getGripContainer ( ) ;
2010-07-20 07:59:47 -05:00
// create segline
segLine = document . createElementNS ( svgns , "path" ) ;
assignAttributes ( segLine , {
'id' : "segline_" + index ,
'display' : 'none' ,
'fill' : "none" ,
'stroke' : "#0FF" ,
'stroke-width' : 2 ,
'style' : 'pointer-events:none' ,
'd' : 'M0,0 0,0'
} ) ;
pointGripContainer . appendChild ( segLine ) ;
}
if ( update ) {
var prev = seg . prev ;
if ( ! prev ) {
segLine . setAttribute ( "display" , "none" ) ;
return segLine ;
}
var pt = getGripPt ( prev ) ;
// Set start point
replacePathSeg ( 2 , 0 , [ pt . x , pt . y ] , segLine ) ;
var pts = ptObjToArr ( seg . type , seg . item , true ) ;
for ( var i = 0 ; i < pts . length ; i += 2 ) {
var pt = getGripPt ( seg , { x : pts [ i ] , y : pts [ i + 1 ] } ) ;
pts [ i ] = pt . x ;
pts [ i + 1 ] = pt . y ;
}
replacePathSeg ( seg . type , 1 , pts , segLine ) ;
}
return segLine ;
}
var getControlPoints = function ( seg ) {
var item = seg . item ;
var index = seg . index ;
if ( ! ( "x1" in item ) || ! ( "x2" in item ) ) return null ;
var cpt = { } ;
var pointGripContainer = getGripContainer ( ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// Note that this is intentionally not seg.prev.item
var prev = path . segs [ index - 1 ] . item ;
var seg _items = [ prev , item ] ;
for ( var i = 1 ; i < 3 ; i ++ ) {
var id = index + 'c' + i ;
var ctrlLine = cpt [ 'c' + i + '_line' ] = getElem ( "ctrlLine_" + id ) ;
if ( ! ctrlLine ) {
ctrlLine = document . createElementNS ( svgns , "line" ) ;
assignAttributes ( ctrlLine , {
'id' : "ctrlLine_" + id ,
'stroke' : "#555" ,
'stroke-width' : 1 ,
"style" : "pointer-events:none"
} ) ;
pointGripContainer . appendChild ( ctrlLine ) ;
}
var pt = getGripPt ( seg , { x : item [ 'x' + i ] , y : item [ 'y' + i ] } ) ;
var gpt = getGripPt ( seg , { x : seg _items [ i - 1 ] . x , y : seg _items [ i - 1 ] . y } ) ;
assignAttributes ( ctrlLine , {
'x1' : pt . x ,
'y1' : pt . y ,
'x2' : gpt . x ,
'y2' : gpt . y ,
'display' : "inline"
} ) ;
cpt [ 'c' + i + '_line' ] = ctrlLine ;
var pointGrip = cpt [ 'c' + i ] = getElem ( "ctrlpointgrip_" + id ) ;
2010-02-26 14:51:27 -06:00
// create it
if ( ! pointGrip ) {
pointGrip = document . createElementNS ( svgns , "circle" ) ;
assignAttributes ( pointGrip , {
2010-07-20 07:59:47 -05:00
'id' : "ctrlpointgrip_" + id ,
2010-02-26 14:51:27 -06:00
'display' : "none" ,
'r' : 4 ,
'fill' : "#0FF" ,
2010-07-20 07:59:47 -05:00
'stroke' : "#55F" ,
'stroke-width' : 1 ,
2010-02-26 14:51:27 -06:00
'cursor' : 'move' ,
'style' : 'pointer-events:all' ,
2010-07-20 07:59:47 -05:00
'xlink:title' : uiStrings . pathCtrlPtTooltip
2010-02-26 14:51:27 -06:00
} ) ;
2010-07-20 07:59:47 -05:00
pointGripContainer . appendChild ( pointGrip ) ;
2010-03-18 10:32:47 -05:00
}
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
assignAttributes ( pointGrip , {
'cx' : pt . x ,
'cy' : pt . y ,
'display' : "inline"
} ) ;
cpt [ 'c' + i ] = pointGrip ;
}
return cpt ;
}
function getGripPt ( seg , alt _pt ) {
var out = {
x : alt _pt ? alt _pt . x : seg . item . x ,
y : alt _pt ? alt _pt . y : seg . item . y
} , path = seg . path ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( path . matrix ) {
var pt = transformPoint ( out . x , out . y , path . matrix ) ;
out = pt ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
out . x *= current _zoom ;
out . y *= current _zoom ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
return out ;
}
function getPointFromGrip ( pt , path ) {
var out = {
x : pt . x ,
y : pt . y
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( path . matrix ) {
var pt = transformPoint ( out . x , out . y , path . imatrix ) ;
out . x = pt . x ;
out . y = pt . y ;
2010-03-18 10:32:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
out . x /= current _zoom ;
out . y /= current _zoom ;
return out ;
}
function Segment ( index , item ) {
var s = this ;
s . index = index ;
s . selected = false ;
s . type = item . pathSegType ;
var grip ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
s . addGrip = function ( ) {
grip = s . ptgrip = getPointGrip ( s , true ) ;
s . ctrlpts = getControlPoints ( s , true ) ;
s . segsel = getSegSelector ( s , true ) ;
2010-03-18 10:32:47 -05:00
}
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
s . item = item ;
s . show = function ( y ) {
if ( grip ) {
grip . setAttribute ( "display" , y ? "inline" : "none" ) ;
s . segsel . setAttribute ( "display" , y ? "inline" : "none" ) ;
// Show/hide all control points if available
s . showCtrlPts ( y ) ;
2010-02-26 14:51:27 -06:00
}
}
2010-07-20 07:59:47 -05:00
s . select = function ( y ) {
if ( grip ) {
grip . setAttribute ( "stroke" , y ? "#0FF" : "#00F" ) ;
s . segsel . setAttribute ( "display" , y ? "inline" : "none" ) ;
if ( s . ctrlpts ) {
s . selectCtrls ( y ) ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
s . selected = y ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
}
s . selectCtrls = function ( y ) {
$ ( '#ctrlpointgrip_' + s . index + 'c1, #ctrlpointgrip_' + s . index + 'c2' ) . attr ( 'fill' , y ? '#0FF' : '#EEE' ) ;
}
s . update = function ( full ) {
item = s . item ;
if ( grip ) {
var pt = getGripPt ( s ) ;
assignAttributes ( grip , {
'cx' : pt . x ,
'cy' : pt . y
} ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
getSegSelector ( s , true ) ;
2010-02-26 14:51:27 -06:00
if ( s . ctrlpts ) {
2010-07-20 07:59:47 -05:00
if ( full ) {
s . item = path . elem . pathSegList . getItem ( s . index ) ;
s . type = s . item . pathSegType ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
getControlPoints ( s ) ;
}
// this.segsel.setAttribute("display", y?"inline":"none");
2010-02-05 21:36:35 -06:00
}
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
s . move = function ( dx , dy ) {
var item = s . item ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
var cur = s ;
if ( cur . ctrlpts ) {
var cur _pts = [ item . x += dx , item . y += dy ,
item . x1 , item . y1 , item . x2 += dx , item . y2 += dy ] ;
} else {
var cur _pts = [ item . x += dx , item . y += dy ] ;
}
replacePathSeg ( cur . type , cur . index , cur _pts ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( s . next && s . next . ctrlpts ) {
var next = s . next . item ;
var next _pts = [ next . x , next . y ,
next . x1 += dx , next . y1 += dy , next . x2 , next . y2 ] ;
replacePathSeg ( s . next . type , s . next . index , next _pts ) ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
if ( s . mate ) {
// The last point of a closed subpath has a "mate",
// which is the "M" segment of the subpath
var item = s . mate . item ;
var pts = [ item . x += dx , item . y += dy ] ;
replacePathSeg ( s . mate . type , s . mate . index , pts ) ;
// Has no grip, so does not need "updating"?
2010-02-05 21:36:35 -06:00
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
s . update ( true ) ;
if ( s . next ) s . next . update ( true ) ;
}
s . setLinked = function ( num ) {
var seg , anum , pt ;
if ( num == 2 ) {
anum = 1 ;
seg = s . next ;
if ( ! seg ) return ;
pt = s . item ;
} else {
anum = 2 ;
seg = s . prev ;
if ( ! seg ) return ;
pt = seg . item ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
var item = seg . item ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
item [ 'x' + anum ] = pt . x + ( pt . x - s . item [ 'x' + num ] ) ;
item [ 'y' + anum ] = pt . y + ( pt . y - s . item [ 'y' + num ] ) ;
var pts = [ item . x , item . y ,
item . x1 , item . y1 , item . x2 , item . y2 ] ;
2010-04-28 00:22:49 -05:00
2010-07-20 07:59:47 -05:00
replacePathSeg ( seg . type , seg . index , pts ) ;
seg . update ( true ) ;
}
s . moveCtrl = function ( num , dx , dy ) {
var item = s . item ;
item [ 'x' + num ] += dx ;
item [ 'y' + num ] += dy ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
var pts = [ item . x , item . y ,
item . x1 , item . y1 , item . x2 , item . y2 ] ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
replacePathSeg ( s . type , s . index , pts ) ;
s . update ( true ) ;
}
s . setType = function ( new _type , pts ) {
replacePathSeg ( new _type , index , pts ) ;
s . type = new _type ;
s . item = path . elem . pathSegList . getItem ( index ) ;
s . showCtrlPts ( new _type === 6 ) ;
s . ctrlpts = getControlPoints ( s ) ;
s . update ( true ) ;
}
s . showCtrlPts = function ( y ) {
if ( s . ctrlpts ) {
for ( var o in s . ctrlpts ) {
s . ctrlpts [ o ] . setAttribute ( "display" , y ? "inline" : "none" ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
}
function Path ( elem ) {
if ( ! elem || elem . tagName !== "path" ) return false ;
var p = path = this ;
this . elem = elem ;
this . segs = [ ] ;
this . selected _pts = [ ] ;
// Reset path data
this . init = function ( ) {
// Hide all grips, etc
$ ( getGripContainer ( ) ) . find ( "*" ) . attr ( "display" , "none" ) ;
var segList = elem . pathSegList ;
var len = segList . numberOfItems ;
p . segs = [ ] ;
p . selected _pts = [ ] ;
p . first _seg = null ;
// Set up segs array
for ( var i = 0 ; i < len ; i ++ ) {
var item = segList . getItem ( i ) ;
var segment = new Segment ( i , item ) ;
segment . path = p ;
p . segs . push ( segment ) ;
}
var segs = p . segs ;
var start _i = null ;
for ( var i = 0 ; i < len ; i ++ ) {
var seg = segs [ i ] ;
var next _seg = ( i + 1 ) >= len ? null : segs [ i + 1 ] ;
var prev _seg = ( i - 1 ) < 0 ? null : segs [ i - 1 ] ;
if ( seg . type === 2 ) {
if ( prev _seg && prev _seg . type !== 1 ) {
// New sub-path, last one is open,
// so add a grip to last sub-path's first point
var start _seg = segs [ start _i ] ;
start _seg . next = segs [ start _i + 1 ] ;
start _seg . next . prev = start _seg ;
start _seg . addGrip ( ) ;
2010-03-25 02:22:55 -05:00
}
2010-07-20 07:59:47 -05:00
// Remember that this is a starter seg
start _i = i ;
} else if ( next _seg && next _seg . type === 1 ) {
// This is the last real segment of a closed sub-path
// Next is first seg after "M"
seg . next = segs [ start _i + 1 ] ;
// First seg after "M"'s prev is this
seg . next . prev = seg ;
seg . mate = segs [ start _i ] ;
seg . addGrip ( ) ;
if ( p . first _seg == null ) {
p . first _seg = seg ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
} else if ( ! next _seg ) {
if ( seg . type !== 1 ) {
// Last seg, doesn't close so add a grip
// to last sub-path's first point
var start _seg = segs [ start _i ] ;
start _seg . next = segs [ start _i + 1 ] ;
start _seg . next . prev = start _seg ;
start _seg . addGrip ( ) ;
seg . addGrip ( ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( ! p . first _seg ) {
// Open path, so set first as real first and add grip
p . first _seg = segs [ start _i ] ;
2010-03-04 00:05:36 -06:00
}
2010-07-20 07:59:47 -05:00
}
} else if ( seg . type !== 1 ) {
// Regular segment, so add grip and its "next"
seg . addGrip ( ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
// Don't set its "next" if it's an "M"
if ( next _seg && next _seg . type !== 2 ) {
seg . next = next _seg ;
seg . next . prev = seg ;
2010-02-26 14:51:27 -06:00
}
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return p ;
}
this . init ( ) ;
// Update position of all points
this . update = function ( ) {
if ( getRotationAngle ( p . elem ) ) {
p . matrix = getMatrix ( path . elem ) ;
p . imatrix = p . matrix . inverse ( ) ;
}
p . eachSeg ( function ( i ) {
this . item = elem . pathSegList . getItem ( i ) ;
this . update ( ) ;
} ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
return p ;
}
this . eachSeg = function ( fn ) {
var len = p . segs . length
for ( var i = 0 ; i < len ; i ++ ) {
var ret = fn . call ( p . segs [ i ] , i ) ;
if ( ret === false ) break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
this . addSeg = function ( index ) {
// Adds a new segment
var seg = p . segs [ index ] ;
if ( ! seg . prev ) return ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var prev = seg . prev ;
var newseg ;
switch ( seg . item . pathSegType ) {
case 4 :
var new _x = ( seg . item . x + prev . item . x ) / 2 ;
var new _y = ( seg . item . y + prev . item . y ) / 2 ;
newseg = elem . createSVGPathSegLinetoAbs ( new _x , new _y ) ;
break ;
case 6 : //make it a curved segment to preserve the shape (WRS)
// http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation
var p0 _x = ( prev . item . x + seg . item . x1 ) / 2 ;
var p1 _x = ( seg . item . x1 + seg . item . x2 ) / 2 ;
var p2 _x = ( seg . item . x2 + seg . item . x ) / 2 ;
var p01 _x = ( p0 _x + p1 _x ) / 2 ;
var p12 _x = ( p1 _x + p2 _x ) / 2 ;
var new _x = ( p01 _x + p12 _x ) / 2 ;
var p0 _y = ( prev . item . y + seg . item . y1 ) / 2 ;
var p1 _y = ( seg . item . y1 + seg . item . y2 ) / 2 ;
var p2 _y = ( seg . item . y2 + seg . item . y ) / 2 ;
var p01 _y = ( p0 _y + p1 _y ) / 2 ;
var p12 _y = ( p1 _y + p2 _y ) / 2 ;
var new _y = ( p01 _y + p12 _y ) / 2 ;
newseg = elem . createSVGPathSegCurvetoCubicAbs ( new _x , new _y , p0 _x , p0 _y , p01 _x , p01 _y ) ;
var pts = [ seg . item . x , seg . item . y , p12 _x , p12 _y , p2 _x , p2 _y ] ;
replacePathSeg ( seg . type , index , pts ) ;
break ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
insertItemBefore ( elem , newseg , index ) ;
}
this . deleteSeg = function ( index ) {
var seg = p . segs [ index ] ;
var list = elem . pathSegList ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
seg . show ( false ) ;
var next = seg . next ;
if ( seg . mate ) {
// Make the next point be the "M" point
var pt = [ next . item . x , next . item . y ] ;
replacePathSeg ( 2 , next . index , pt ) ;
// Reposition last node
replacePathSeg ( 4 , seg . index , pt ) ;
list . removeItem ( seg . mate . index ) ;
} else if ( ! seg . prev ) {
// First node of open path, make next point the M
var item = seg . item ;
var pt = [ next . item . x , next . item . y ] ;
replacePathSeg ( 2 , seg . next . index , pt ) ;
list . removeItem ( index ) ;
} else {
list . removeItem ( index ) ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
}
this . endChanges = function ( text ) {
if ( isWebkit ) resetD ( p . elem ) ;
var cmd = new ChangeElementCommand ( elem , { d : p . last _d } , text ) ;
addCommandToHistory ( cmd ) ;
call ( "changed" , [ elem ] ) ;
}
this . subpathIsClosed = function ( index ) {
var closed = false ;
// Check if subpath is already open
path . eachSeg ( function ( i ) {
if ( i <= index ) return true ;
if ( this . type === 2 ) {
// Found M first, so open
return false ;
} else if ( this . type === 1 ) {
// Found Z first, so closed
closed = true ;
return false ;
}
} ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
return closed ;
}
this . addPtsToSelection = function ( indexes ) {
if ( ! $ . isArray ( indexes ) ) indexes = [ indexes ] ;
for ( var i = 0 ; i < indexes . length ; i ++ ) {
var index = indexes [ i ] ;
var seg = p . segs [ index ] ;
if ( seg . ptgrip ) {
if ( $ . inArray ( index , p . selected _pts ) == - 1 && index >= 0 ) {
p . selected _pts . push ( index ) ;
}
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
} ;
p . selected _pts . sort ( ) ;
var i = p . selected _pts . length ,
grips = new Array ( i ) ;
// Loop through points to be selected and highlight each
while ( i -- ) {
var pt = p . selected _pts [ i ] ;
var seg = p . segs [ pt ] ;
seg . select ( true ) ;
grips [ i ] = seg . ptgrip ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
// TODO: Correct this:
pathActions . canDeleteNodes = true ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
pathActions . closed _subpath = p . subpathIsClosed ( p . selected _pts [ 0 ] ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
call ( "selected" , grips ) ;
}
this . removePtFromSelection = function ( index ) {
var pos = $ . inArray ( index , p . selected _pts ) ;
if ( pos == - 1 ) {
return ;
}
p . segs [ index ] . select ( false ) ;
p . selected _pts . splice ( pos , 1 ) ;
}
this . clearSelection = function ( ) {
p . eachSeg ( function ( i ) {
this . select ( false ) ;
} ) ;
p . selected _pts = [ ] ;
}
this . selectPt = function ( pt , ctrl _num ) {
p . clearSelection ( ) ;
if ( pt == null ) {
p . eachSeg ( function ( i ) {
if ( this . prev ) {
pt = i ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
}
p . addPtsToSelection ( pt ) ;
if ( ctrl _num ) {
p . dragctrl = ctrl _num ;
if ( link _control _pts ) {
p . segs [ pt ] . setLinked ( ctrl _num ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
this . storeD = function ( ) {
this . last _d = elem . getAttribute ( 'd' ) ;
}
this . show = function ( y ) {
// Shows this path's segment grips
p . eachSeg ( function ( ) {
this . show ( y ) ;
} ) ;
if ( y ) {
p . selectPt ( p . first _seg . index ) ;
}
2010-02-26 14:51:27 -06:00
return p ;
2010-02-05 21:36:35 -06:00
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// Move selected points
this . movePts = function ( d _x , d _y ) {
var i = p . selected _pts . length ;
while ( i -- ) {
var seg = p . segs [ p . selected _pts [ i ] ] ;
seg . move ( d _x , d _y ) ;
}
}
this . moveCtrl = function ( d _x , d _y ) {
var seg = p . segs [ p . selected _pts [ 0 ] ] ;
seg . moveCtrl ( p . dragctrl , d _x , d _y ) ;
if ( link _control _pts ) {
seg . setLinked ( p . dragctrl ) ;
}
}
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
this . setSegType = function ( new _type ) {
p . storeD ( ) ;
var i = p . selected _pts . length ;
var text ;
while ( i -- ) {
var sel _pt = p . selected _pts [ i ] ;
// Selected seg
var cur = p . segs [ sel _pt ] ;
var prev = cur . prev ;
if ( ! prev ) continue ;
if ( ! new _type ) { // double-click, so just toggle
text = "Toggle Path Segment Type" ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
// Toggle segment to curve/straight line
var old _type = cur . type ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
new _type = ( old _type == 6 ) ? 4 : 6 ;
}
new _type = new _type - 0 ;
var cur _x = cur . item . x ;
var cur _y = cur . item . y ;
var prev _x = prev . item . x ;
var prev _y = prev . item . y ;
var points ;
switch ( new _type ) {
case 6 :
if ( cur . olditem ) {
var old = cur . olditem ;
points = [ cur _x , cur _y , old . x1 , old . y1 , old . x2 , old . y2 ] ;
} else {
var diff _x = cur _x - prev _x ;
var diff _y = cur _y - prev _y ;
// get control points from straight line segment
/ *
var ct1 _x = ( prev _x + ( diff _y / 2 ) ) ;
var ct1 _y = ( prev _y - ( diff _x / 2 ) ) ;
var ct2 _x = ( cur _x + ( diff _y / 2 ) ) ;
var ct2 _y = ( cur _y - ( diff _x / 2 ) ) ;
* /
//create control points on the line to preserve the shape (WRS)
var ct1 _x = ( prev _x + ( diff _x / 3 ) ) ;
var ct1 _y = ( prev _y + ( diff _y / 3 ) ) ;
var ct2 _x = ( cur _x - ( diff _x / 3 ) ) ;
var ct2 _y = ( cur _y - ( diff _y / 3 ) ) ;
points = [ cur _x , cur _y , ct1 _x , ct1 _y , ct2 _x , ct2 _y ] ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
break ;
case 4 :
points = [ cur _x , cur _y ] ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// Store original prevve segment nums
cur . olditem = cur . item ;
break ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
cur . setType ( new _type , points ) ;
}
path . endChanges ( text ) ;
return ;
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
}
function getPath ( elem ) {
var p = pathData [ elem . id ] ;
if ( ! p ) p = pathData [ elem . id ] = new Path ( elem ) ;
return p ;
}
var pathFuncs = [ ] ,
current _path = null ,
current _path _pts = [ ] ,
link _control _pts = false ,
hasMoved = false ;
// This function converts a polyline (created by the fh_path tool) into
// a path element and coverts every three line segments into a single bezier
// curve in an attempt to smooth out the free-hand
var smoothPolylineIntoPath = function ( element ) {
var points = element . points ;
var N = points . numberOfItems ;
if ( N >= 4 ) {
// loop through every 3 points and convert to a cubic bezier curve segment
//
// NOTE: this is cheating, it means that every 3 points has the potential to
// be a corner instead of treating each point in an equal manner. In general,
// this technique does not look that good.
//
// I am open to better ideas!
//
// Reading:
// - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm
// - http://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
// - http://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
// - http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
var curpos = points . getItem ( 0 ) , prevCtlPt = null ;
var d = [ ] ;
d . push ( [ "M" , curpos . x , "," , curpos . y , " C" ] . join ( "" ) ) ;
for ( var i = 1 ; i <= ( N - 4 ) ; i += 3 ) {
var ct1 = points . getItem ( i ) ;
var ct2 = points . getItem ( i + 1 ) ;
var end = points . getItem ( i + 2 ) ;
// if the previous segment had a control point, we want to smooth out
// the control points on both sides
if ( prevCtlPt ) {
var newpts = smoothControlPoints ( prevCtlPt , ct1 , curpos ) ;
if ( newpts && newpts . length == 2 ) {
var prevArr = d [ d . length - 1 ] . split ( ',' ) ;
prevArr [ 2 ] = newpts [ 0 ] . x ;
prevArr [ 3 ] = newpts [ 0 ] . y ;
d [ d . length - 1 ] = prevArr . join ( ',' ) ;
ct1 = newpts [ 1 ] ;
2010-04-01 23:56:21 -05:00
}
2010-07-20 07:59:47 -05:00
}
d . push ( [ ct1 . x , ct1 . y , ct2 . x , ct2 . y , end . x , end . y ] . join ( ',' ) ) ;
curpos = end ;
prevCtlPt = ct2 ;
2010-03-18 10:32:47 -05:00
}
2010-07-20 07:59:47 -05:00
// handle remaining line segments
d . push ( "L" ) ;
for ( ; i < N ; ++ i ) {
var pt = points . getItem ( i ) ;
d . push ( [ pt . x , pt . y ] . join ( "," ) ) ;
}
d = d . join ( " " ) ;
// create new path element
element = addSvgElementFromJson ( {
"element" : "path" ,
"curStyles" : true ,
"attr" : {
"id" : getId ( ) ,
"d" : d ,
"fill" : "none"
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
call ( "changed" , [ element ] ) ;
}
return element ;
} ;
// This replaces the segment at the given index. Type is given as number.
var replacePathSeg = function ( type , index , pts , elem ) {
var path = elem || retPath ( ) . elem ;
var func = 'createSVGPathSeg' + pathFuncs [ type ] ;
var seg = path [ func ] . apply ( path , pts ) ;
if ( support . pathReplaceItem ) {
path . pathSegList . replaceItem ( seg , index ) ;
} else {
var segList = path . pathSegList ;
var len = segList . numberOfItems ;
var arr = [ ] ;
for ( var i = 0 ; i < len ; i ++ ) {
var cur _seg = segList . getItem ( i ) ;
arr . push ( cur _seg )
}
segList . clear ( ) ;
for ( var i = 0 ; i < len ; i ++ ) {
if ( i == index ) {
segList . appendItem ( seg ) ;
} else {
segList . appendItem ( arr [ i ] ) ;
2010-02-05 21:36:35 -06:00
}
}
}
2010-07-20 07:59:47 -05:00
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// If the path was rotated, we must now pay the piper:
// Every path point must be rotated into the rotated coordinate system of
// its old center, then determine the new center, then rotate it back
// This is because we want the path to remember its rotation
// TODO: This is still using ye olde transform methods, can probably
// be optimized or even taken care of by recalculateDimensions
var recalcRotatedPath = function ( ) {
var current _path = path . elem ;
var angle = getRotationAngle ( current _path , true ) ;
if ( ! angle ) return ;
selectedBBoxes [ 0 ] = path . oldbbox ;
var box = getBBox ( current _path ) ,
oldbox = selectedBBoxes [ 0 ] ,
oldcx = oldbox . x + oldbox . width / 2 ,
oldcy = oldbox . y + oldbox . height / 2 ,
newcx = box . x + box . width / 2 ,
newcy = box . y + box . height / 2 ,
// un-rotate the new center to the proper position
dx = newcx - oldcx ,
dy = newcy - oldcy ,
r = Math . sqrt ( dx * dx + dy * dy ) ,
theta = Math . atan2 ( dy , dx ) + angle ;
newcx = r * Math . cos ( theta ) + oldcx ;
newcy = r * Math . sin ( theta ) + oldcy ;
var getRotVals = function ( x , y ) {
dx = x - oldcx ;
dy = y - oldcy ;
// rotate the point around the old center
r = Math . sqrt ( dx * dx + dy * dy ) ;
theta = Math . atan2 ( dy , dx ) + angle ;
dx = r * Math . cos ( theta ) + oldcx ;
dy = r * Math . sin ( theta ) + oldcy ;
// dx,dy should now hold the actual coordinates of each
// point after being rotated
// now we want to rotate them around the new center in the reverse direction
dx -= newcx ;
dy -= newcy ;
r = Math . sqrt ( dx * dx + dy * dy ) ;
theta = Math . atan2 ( dy , dx ) - angle ;
return { 'x' : ( r * Math . cos ( theta ) + newcx ) / 1 ,
'y' : ( r * Math . sin ( theta ) + newcy ) / 1 } ;
}
var list = current _path . pathSegList ,
i = list . numberOfItems ;
while ( i ) {
i -= 1 ;
var seg = list . getItem ( i ) ,
type = seg . pathSegType ;
if ( type == 1 ) continue ;
var rvals = getRotVals ( seg . x , seg . y ) ,
points = [ rvals . x , rvals . y ] ;
if ( seg . x1 != null && seg . x2 != null ) {
c _vals1 = getRotVals ( seg . x1 , seg . y1 ) ;
c _vals2 = getRotVals ( seg . x2 , seg . y2 ) ;
points . splice ( points . length , 0 , c _vals1 . x , c _vals1 . y , c _vals2 . x , c _vals2 . y ) ;
}
replacePathSeg ( type , i , points ) ;
} // loop for each point
box = getBBox ( current _path ) ;
selectedBBoxes [ 0 ] . x = box . x ; selectedBBoxes [ 0 ] . y = box . y ;
selectedBBoxes [ 0 ] . width = box . width ; selectedBBoxes [ 0 ] . height = box . height ;
// now we must set the new transform to be rotated around the new center
var R _nc = svgroot . createSVGTransform ( ) ,
tlist = getTransformList ( current _path ) ;
R _nc . setRotate ( ( angle * 180.0 / Math . PI ) , newcx , newcy ) ;
tlist . replaceItem ( R _nc , 0 ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
return {
init : function ( ) {
pathFuncs = [ 0 , 'ClosePath' ] ;
var pathFuncsStrs = [ 'Moveto' , 'Lineto' , 'CurvetoCubic' , 'CurvetoQuadratic' , 'Arc' , 'LinetoHorizontal' , 'LinetoVertical' , 'CurvetoCubicSmooth' , 'CurvetoQuadraticSmooth' ] ;
$ . each ( pathFuncsStrs , function ( i , s ) { pathFuncs . push ( s + 'Abs' ) ; pathFuncs . push ( s + 'Rel' ) ; } ) ;
} ,
getPath : function ( ) {
return path ;
} ,
mouseDown : function ( evt , mouse _target , start _x , start _y ) {
if ( current _mode == "path" ) return ;
// TODO: Make sure current_path isn't null at this point
if ( ! path ) return ;
path . storeD ( ) ;
var id = evt . target . id ;
if ( id . substr ( 0 , 14 ) == "pathpointgrip_" ) {
// Select this point
var cur _pt = path . cur _pt = parseInt ( id . substr ( 14 ) ) ;
path . dragging = [ start _x , start _y ] ;
var seg = path . segs [ cur _pt ] ;
// only clear selection if shift is not pressed (otherwise, add
// node to selection)
if ( ! evt . shiftKey ) {
if ( path . selected _pts . length <= 1 || ! seg . selected ) {
path . clearSelection ( ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
path . addPtsToSelection ( cur _pt ) ;
} else if ( seg . selected ) {
path . removePtFromSelection ( cur _pt ) ;
} else {
path . addPtsToSelection ( cur _pt ) ;
2010-02-26 14:51:27 -06:00
}
2010-07-20 07:59:47 -05:00
} else if ( id . indexOf ( "ctrlpointgrip_" ) == 0 ) {
path . dragging = [ start _x , start _y ] ;
var parts = id . split ( '_' ) [ 1 ] . split ( 'c' ) ;
var cur _pt = parts [ 0 ] - 0 ;
var ctrl _num = parts [ 1 ] - 0 ;
path . selectPt ( cur _pt , ctrl _num ) ;
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// Start selection box
if ( ! path . dragging ) {
if ( rubberBox == null ) {
rubberBox = selectorManager . getRubberBandBox ( ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
assignAttributes ( rubberBox , {
'x' : start _x * current _zoom ,
'y' : start _y * current _zoom ,
'width' : 0 ,
'height' : 0 ,
'display' : 'inline'
} , 100 ) ;
}
} ,
mouseMove : function ( mouse _x , mouse _y ) {
hasMoved = true ;
if ( current _mode == "path" ) {
var line = getElem ( "path_stretch_line" ) ;
if ( line ) {
line . setAttribute ( "x2" , mouse _x ) ;
line . setAttribute ( "y2" , mouse _y ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return ;
}
// if we are dragging a point, let's move it
if ( path . dragging ) {
var pt = getPointFromGrip ( {
x : path . dragging [ 0 ] ,
y : path . dragging [ 1 ]
} , path ) ;
var mpt = getPointFromGrip ( {
x : mouse _x ,
y : mouse _y
} , path ) ;
var diff _x = mpt . x - pt . x ;
var diff _y = mpt . y - pt . y ;
path . dragging = [ mouse _x , mouse _y ] ;
if ( path . dragctrl ) {
path . moveCtrl ( diff _x , diff _y ) ;
2010-02-05 21:36:35 -06:00
} else {
2010-07-20 07:59:47 -05:00
path . movePts ( diff _x , diff _y ) ;
}
} else {
path . selected _pts = [ ] ;
path . eachSeg ( function ( i ) {
var seg = this ;
if ( ! seg . next && ! seg . prev ) return ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var item = seg . item ;
var rbb = rubberBox . getBBox ( ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var pt = getGripPt ( seg ) ;
var pt _bb = {
x : pt . x ,
y : pt . y ,
width : 0 ,
height : 0
} ;
var sel = Utils . rectsIntersect ( rbb , pt _bb ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
this . select ( sel ) ;
//Note that addPtsToSelection is not being run
if ( sel ) path . selected _pts . push ( seg . index ) ;
} ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
}
} ,
mouseUp : function ( evt , element , mouse _x , mouse _y ) {
// Create mode
if ( current _mode == "path" ) {
var x = mouse _x / current _zoom ,
y = mouse _y / current _zoom ,
stretchy = getElem ( "path_stretch_line" ) ;
2010-09-10 15:19:23 -05:00
if ( svgEditor . curConfig . gridSnapping ) {
x = Utils . snapToGrid ( x ) ;
y = Utils . snapToGrid ( y ) ;
}
2010-07-20 07:59:47 -05:00
if ( ! stretchy ) {
stretchy = document . createElementNS ( svgns , "line" ) ;
assignAttributes ( stretchy , {
'id' : "path_stretch_line" ,
'stroke' : "#22C" ,
'stroke-width' : "0.5"
} ) ;
stretchy = getElem ( "selectorParentGroup" ) . appendChild ( stretchy ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
stretchy . setAttribute ( "display" , "inline" ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
var keep = null ;
// if pts array is empty, create path element with M at current point
if ( current _path _pts . length == 0 ) {
current _path _pts . push ( x ) ;
current _path _pts . push ( y ) ;
d _attr = "M" + x + "," + y + " " ;
addSvgElementFromJson ( {
"element" : "path" ,
"curStyles" : true ,
"attr" : {
"d" : d _attr ,
"id" : getNextId ( ) ,
"opacity" : cur _shape . opacity / 2 ,
}
} ) ;
// set stretchy line to first point
assignAttributes ( stretchy , {
'x1' : mouse _x ,
'y1' : mouse _y ,
'x2' : mouse _x ,
'y2' : mouse _y
} ) ;
var index = subpath ? path . segs . length : 0 ;
addPointGrip ( index , mouse _x , mouse _y ) ;
}
else {
// determine if we clicked on an existing point
var i = current _path _pts . length ;
var FUZZ = 6 / current _zoom ;
var clickOnPoint = false ;
while ( i ) {
i -= 2 ;
var px = current _path _pts [ i ] , py = current _path _pts [ i + 1 ] ;
// found a matching point
if ( x >= ( px - FUZZ ) && x <= ( px + FUZZ ) && y >= ( py - FUZZ ) && y <= ( py + FUZZ ) ) {
clickOnPoint = true ;
break ;
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// get path element that we are in the process of creating
var id = getId ( ) ;
// Remove previous path object if previously created
if ( id in pathData ) delete pathData [ id ] ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var newpath = getElem ( id ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
var len = current _path _pts . length ;
// if we clicked on an existing point, then we are done this path, commit it
// (i,i+1) are the x,y that were clicked on
if ( clickOnPoint ) {
// if clicked on any other point but the first OR
// the first point was clicked on and there are less than 3 points
// then leave the path open
// otherwise, close the path
if ( i == 0 && len >= 6 ) {
// Create end segment
var abs _x = current _path _pts [ 0 ] ;
var abs _y = current _path _pts [ 1 ] ;
d _attr += [ 'L' , abs _x , ',' , abs _y , 'z' ] . join ( '' ) ;
newpath . setAttribute ( "d" , d _attr ) ;
} else if ( len < 3 ) {
keep = false ;
return keep ;
}
$ ( stretchy ) . remove ( ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
// this will signal to commit the path
element = newpath ;
current _path _pts = [ ] ;
started = false ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
if ( subpath ) {
if ( path . matrix ) {
remapElement ( newpath , { } , path . matrix . inverse ( ) ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var new _d = newpath . getAttribute ( "d" ) ;
var orig _d = $ ( path . elem ) . attr ( "d" ) ;
$ ( path . elem ) . attr ( "d" , orig _d + new _d ) ;
$ ( newpath ) . remove ( ) ;
if ( path . matrix ) {
recalcRotatedPath ( ) ;
2010-03-04 00:05:36 -06:00
}
2010-07-20 07:59:47 -05:00
path . init ( ) ;
pathActions . toEditMode ( path . elem ) ;
path . selectPt ( ) ;
return false ;
}
}
// else, create a new point, append to pts array, update path element
else {
// Checks if current target or parents are #svgcontent
if ( ! $ . contains ( container , getMouseTarget ( evt ) ) ) {
// Clicked outside canvas, so don't make point
console . log ( "Clicked outside canvas" ) ;
return false ;
2010-02-05 21:36:35 -06:00
}
2010-02-12 12:34:56 -06:00
2010-07-20 07:59:47 -05:00
var lastx = current _path _pts [ len - 2 ] , lasty = current _path _pts [ len - 1 ] ;
2010-05-26 14:37:55 -05:00
2010-07-20 07:59:47 -05:00
if ( evt . shiftKey ) { var xya = Utils . snapToAngle ( lastx , lasty , x , y ) ; x = xya . x ; y = xya . y ; }
// we store absolute values in our path points array for easy checking above
current _path _pts . push ( x ) ;
current _path _pts . push ( y ) ;
d _attr += "L" + round ( x ) + "," + round ( y ) + " " ;
2010-05-26 14:37:55 -05:00
2010-07-20 07:59:47 -05:00
newpath . setAttribute ( "d" , d _attr ) ;
2010-09-10 15:19:23 -05:00
x *= current _zoom ;
y *= current _zoom ;
2010-07-20 07:59:47 -05:00
// set stretchy line to latest point
assignAttributes ( stretchy , {
'x1' : x ,
'y1' : y ,
'x2' : x ,
'y2' : y
} ) ;
var index = ( current _path _pts . length / 2 - 1 ) ;
if ( subpath ) index += path . segs . length ;
addPointGrip ( index , x , y ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
keep = true ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return {
keep : keep ,
element : element
}
}
// Edit mode
if ( path . dragging ) {
var last _pt = path . cur _pt ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
path . dragging = false ;
path . dragctrl = false ;
path . update ( ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( hasMoved ) {
path . endChanges ( "Move path point(s)" ) ;
}
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
if ( ! evt . shiftKey && ! hasMoved ) {
path . selectPt ( last _pt ) ;
}
}
else if ( rubberBox && rubberBox . getAttribute ( 'display' ) != 'none' ) {
// Done with multi-node-select
rubberBox . setAttribute ( "display" , "none" ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
if ( rubberBox . getAttribute ( 'width' ) <= 2 && rubberBox . getAttribute ( 'height' ) <= 2 ) {
pathActions . toSelectMode ( evt . target ) ;
2010-03-04 00:05:36 -06:00
}
2010-06-16 22:46:12 -05:00
2010-07-20 07:59:47 -05:00
// else, move back to select mode
} else {
pathActions . toSelectMode ( evt . target ) ;
}
hasMoved = false ;
} ,
clearData : function ( ) {
pathData = { } ;
} ,
toEditMode : function ( element ) {
path = getPath ( element ) ;
current _mode = "pathedit" ;
clearSelection ( ) ;
path . show ( true ) . update ( ) ;
path . oldbbox = getBBox ( path . elem ) ;
subpath = false ;
} ,
toSelectMode : function ( elem ) {
var selPath = ( elem == path . elem ) ;
current _mode = "select" ;
path . show ( false ) ;
current _path = false ;
clearSelection ( ) ;
if ( path . matrix ) {
// Rotated, so may need to re-calculate the center
recalcRotatedPath ( ) ;
}
if ( selPath ) {
call ( "selected" , [ elem ] ) ;
addToSelection ( [ elem ] , true ) ;
}
} ,
addSubPath : function ( on ) {
if ( on ) {
// Internally we go into "path" mode, but in the UI it will
// still appear as if in "pathedit" mode.
current _mode = "path" ;
subpath = true ;
} else {
pathActions . clear ( true ) ;
pathActions . toEditMode ( path . elem ) ;
}
} ,
select : function ( target ) {
if ( current _path == target ) {
pathActions . toEditMode ( target ) ;
current _mode = "pathedit" ;
} // going into pathedit mode
else {
current _path = target ;
}
} ,
reorient : function ( ) {
var elem = selectedElements [ 0 ] ;
if ( ! elem ) return ;
var angle = getRotationAngle ( elem ) ;
if ( angle == 0 ) return ;
var batchCmd = new BatchCommand ( "Reorient path" ) ;
var changes = {
d : elem . getAttribute ( 'd' ) ,
transform : elem . getAttribute ( 'transform' )
} ;
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , changes ) ) ;
clearSelection ( ) ;
this . resetOrientation ( elem ) ;
addCommandToHistory ( batchCmd ) ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
// Set matrix to null
getPath ( elem ) . show ( false ) . matrix = null ;
2010-02-26 14:51:27 -06:00
2010-07-20 07:59:47 -05:00
this . clear ( ) ;
addToSelection ( [ elem ] , true ) ;
call ( "changed" , selectedElements ) ;
} ,
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
clear : function ( remove ) {
current _path = null ;
if ( current _mode == "path" && current _path _pts . length > 0 ) {
var elem = getElem ( getId ( ) ) ;
$ ( getElem ( "path_stretch_line" ) ) . remove ( ) ;
$ ( elem ) . remove ( ) ;
$ ( getElem ( "pathpointgrip_container" ) ) . find ( '*' ) . attr ( 'display' , 'none' ) ;
current _path _pts = [ ] ;
started = false ;
} else if ( current _mode == "pathedit" ) {
this . toSelectMode ( ) ;
}
if ( path ) path . init ( ) . show ( false ) ;
} ,
resetOrientation : function ( path ) {
if ( path == null || path . nodeName != 'path' ) return false ;
var tlist = getTransformList ( path ) ;
var m = transformListToTransform ( tlist ) . matrix ;
tlist . clear ( ) ;
path . removeAttribute ( "transform" ) ;
var segList = path . pathSegList ;
// Opera/win/non-EN throws an error here.
// TODO: Find out why!
// Presumed fixed in Opera 10.5, so commented out for now
// try {
var len = segList . numberOfItems ;
// } catch(err) {
// var fixed_d = pathActions.convertPath(path);
// path.setAttribute('d', fixed_d);
// segList = path.pathSegList;
// var len = segList.numberOfItems;
// }
for ( var i = 0 ; i < len ; ++ i ) {
var seg = segList . getItem ( i ) ;
var type = seg . pathSegType ;
if ( type == 1 ) continue ;
var pts = [ ] ;
$ . each ( [ '' , 1 , 2 ] , function ( j , n ) {
var x = seg [ 'x' + n ] , y = seg [ 'y' + n ] ;
if ( x && y ) {
var pt = transformPoint ( x , y , m ) ;
pts . splice ( pts . length , 0 , pt . x , pt . y ) ;
}
} ) ;
replacePathSeg ( type , i , pts , path ) ;
}
} ,
zoomChange : function ( ) {
if ( current _mode == "pathedit" ) {
path . update ( ) ;
}
} ,
getNodePoint : function ( ) {
var sel _pt = path . selected _pts . length ? path . selected _pts [ 0 ] : 1 ;
var seg = path . segs [ sel _pt ] ;
return {
x : seg . item . x ,
y : seg . item . y ,
type : seg . type
} ;
} ,
linkControlPoints : function ( linkPoints ) {
link _control _pts = linkPoints ;
} ,
clonePathNode : function ( ) {
path . storeD ( ) ;
var sel _pts = path . selected _pts ;
var segs = path . segs ;
var i = sel _pts . length ;
var nums = [ ] ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
while ( i -- ) {
var pt = sel _pts [ i ] ;
path . addSeg ( pt ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
nums . push ( pt + i ) ;
nums . push ( pt + i + 1 ) ;
}
path . init ( ) . addPtsToSelection ( nums ) ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
path . endChanges ( "Clone path node(s)" ) ;
} ,
opencloseSubPath : function ( ) {
var sel _pts = path . selected _pts ;
// Only allow one selected node for now
if ( sel _pts . length !== 1 ) return ;
var elem = path . elem ;
var list = elem . pathSegList ;
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
var len = list . numberOfItems ;
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
var index = sel _pts [ 0 ] ;
var open _pt = null ;
var start _item = null ;
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
// Check if subpath is already open
path . eachSeg ( function ( i ) {
if ( this . type === 2 && i <= index ) {
start _item = this . item ;
2010-03-25 02:22:55 -05:00
}
2010-07-20 07:59:47 -05:00
if ( i <= index ) return true ;
if ( this . type === 2 ) {
// Found M first, so open
open _pt = i ;
return false ;
} else if ( this . type === 1 ) {
// Found Z first, so closed
open _pt = false ;
return false ;
2010-03-25 02:22:55 -05:00
}
2010-07-20 07:59:47 -05:00
} ) ;
if ( open _pt == null ) {
// Single path, so close last seg
open _pt = path . segs . length - 1 ;
}
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
if ( open _pt !== false ) {
// Close this path
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
// Create a line going to the previous "M"
var newseg = elem . createSVGPathSegLinetoAbs ( start _item . x , start _item . y ) ;
var closer = elem . createSVGPathSegClosePath ( ) ;
if ( open _pt == path . segs . length - 1 ) {
list . appendItem ( newseg ) ;
list . appendItem ( closer ) ;
} else {
insertItemBefore ( elem , closer , open _pt ) ;
insertItemBefore ( elem , newseg , open _pt ) ;
2010-03-25 02:22:55 -05:00
}
2010-07-20 07:59:47 -05:00
path . init ( ) . selectPt ( open _pt + 1 ) ;
return ;
}
// M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
// M 2,2 L 3,3 L 1,1
// M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
// M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
var seg = path . segs [ index ] ;
if ( seg . mate ) {
list . removeItem ( index ) ; // Removes last "L"
list . removeItem ( index ) ; // Removes the "Z"
path . init ( ) . selectPt ( index - 1 ) ;
return ;
}
var last _m , z _seg ;
// Find this sub-path's closing point and remove
for ( var i = 0 ; i < list . numberOfItems ; i ++ ) {
var item = list . getItem ( i ) ;
if ( item . pathSegType === 2 ) {
// Find the preceding M
last _m = i ;
} else if ( i === index ) {
// Remove it
list . removeItem ( last _m ) ;
2010-03-25 02:22:55 -05:00
// index--;
2010-07-20 07:59:47 -05:00
} else if ( item . pathSegType === 1 && index < i ) {
// Remove the closing seg of this subpath
z _seg = i - 1 ;
list . removeItem ( i ) ;
break ;
2010-03-25 02:22:55 -05:00
}
2010-07-20 07:59:47 -05:00
}
var num = ( index - last _m ) - 1 ;
while ( num -- ) {
insertItemBefore ( elem , list . getItem ( last _m ) , z _seg ) ;
}
var pt = list . getItem ( last _m ) ;
// Make this point the new "M"
replacePathSeg ( 2 , last _m , [ pt . x , pt . y ] ) ;
var i = index
path . init ( ) . selectPt ( 0 ) ;
} ,
deletePathNode : function ( ) {
if ( ! pathActions . canDeleteNodes ) return ;
path . storeD ( ) ;
var sel _pts = path . selected _pts ;
var i = sel _pts . length ;
while ( i -- ) {
var pt = sel _pts [ i ] ;
path . deleteSeg ( pt ) ;
}
// Cleanup
var cleanup = function ( ) {
var segList = path . elem . pathSegList ;
var len = segList . numberOfItems ;
2010-03-25 02:22:55 -05:00
2010-07-20 07:59:47 -05:00
var remItems = function ( pos , count ) {
while ( count -- ) {
segList . removeItem ( pos ) ;
}
2010-03-25 02:22:55 -05:00
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
if ( len <= 1 ) return true ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
while ( len -- ) {
var item = segList . getItem ( len ) ;
if ( item . pathSegType === 1 ) {
var prev = segList . getItem ( len - 1 ) ;
var nprev = segList . getItem ( len - 2 ) ;
if ( prev . pathSegType === 2 ) {
remItems ( len - 1 , 2 ) ;
cleanup ( ) ;
break ;
} else if ( nprev . pathSegType === 2 ) {
remItems ( len - 2 , 3 ) ;
cleanup ( ) ;
break ;
2010-03-12 03:50:23 -06:00
}
2010-07-20 07:59:47 -05:00
} else if ( item . pathSegType === 2 ) {
if ( len > 0 ) {
var prev _type = segList . getItem ( len - 1 ) . pathSegType ;
// Path has M M
if ( prev _type === 2 ) {
remItems ( len - 1 , 1 ) ;
cleanup ( ) ;
break ;
// Entire path ends with Z M
} else if ( prev _type === 1 && segList . numberOfItems - 1 === len ) {
remItems ( len , 1 ) ;
cleanup ( ) ;
break ;
}
}
}
}
return false ;
}
cleanup ( ) ;
// Completely delete a path with 1 or 0 segments
if ( path . elem . pathSegList . numberOfItems <= 1 ) {
pathActions . toSelectMode ( path . elem ) ;
canvas . deleteSelectedElements ( ) ;
return ;
}
path . init ( ) ;
path . clearSelection ( ) ;
// TODO: Find right way to select point now
// path.selectPt(sel_pt);
if ( window . opera ) { // Opera repaints incorrectly
var cp = $ ( path . elem ) ; cp . attr ( 'd' , cp . attr ( 'd' ) ) ;
}
path . endChanges ( "Delete path node(s)" ) ;
} ,
smoothPolylineIntoPath : smoothPolylineIntoPath ,
setSegType : function ( v ) {
path . setSegType ( v ) ;
} ,
moveNode : function ( attr , newValue ) {
var sel _pts = path . selected _pts ;
if ( ! sel _pts . length ) return ;
path . storeD ( ) ;
// Get first selected point
var seg = path . segs [ sel _pts [ 0 ] ] ;
var diff = { x : 0 , y : 0 } ;
diff [ attr ] = newValue - seg . item [ attr ] ;
seg . move ( diff . x , diff . y ) ;
path . endChanges ( "Move path point" ) ;
} ,
fixEnd : function ( elem ) {
// Adds an extra segment if the last seg before a Z doesn't end
// at its M point
// M0,0 L0,100 L100,100 z
var segList = elem . pathSegList ;
var len = segList . numberOfItems ;
var last _m ;
for ( var i = 0 ; i < len ; ++ i ) {
var item = segList . getItem ( i ) ;
if ( item . pathSegType === 2 ) {
last _m = item ;
}
if ( item . pathSegType === 1 ) {
var prev = segList . getItem ( i - 1 ) ;
if ( prev . x != last _m . x || prev . y != last _m . y ) {
// Add an L segment here
var newseg = elem . createSVGPathSegLinetoAbs ( last _m . x , last _m . y ) ;
insertItemBefore ( elem , newseg , i ) ;
// Can this be done better?
pathActions . fixEnd ( elem ) ;
break ;
}
2010-03-12 03:50:23 -06:00
}
2010-07-20 07:59:47 -05:00
}
if ( isWebkit ) resetD ( elem ) ;
} ,
// Convert a path to one with only absolute or relative values
convertPath : function ( path , toRel ) {
var segList = path . pathSegList ;
var len = segList . numberOfItems ;
var curx = 0 , cury = 0 ;
var d = "" ;
var last _m = null ;
for ( var i = 0 ; i < len ; ++ i ) {
var seg = segList . getItem ( i ) ;
// if these properties are not in the segment, set them to zero
var x = seg . x || 0 ,
y = seg . y || 0 ,
x1 = seg . x1 || 0 ,
y1 = seg . y1 || 0 ,
x2 = seg . x2 || 0 ,
y2 = seg . y2 || 0 ;
var type = seg . pathSegType ;
var letter = pathMap [ type ] [ 'to' + ( toRel ? 'Lower' : 'Upper' ) + 'Case' ] ( ) ;
var addToD = function ( pnts , more , last ) {
var str = '' ;
var more = more ? ' ' + more . join ( ' ' ) : '' ;
2010-09-10 15:19:23 -05:00
var last = last ? ' ' + shortFloat ( last ) : '' ;
2010-07-20 07:59:47 -05:00
$ . each ( pnts , function ( i , pnt ) {
pnts [ i ] = shortFloat ( pnt ) ;
} ) ;
d += letter + pnts . join ( ' ' ) + more + last ;
2010-02-05 21:36:35 -06:00
}
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
switch ( type ) {
case 1 : // z,Z closepath (Z/z)
d += "z" ;
break ;
case 12 : // absolute horizontal line (H)
x -= curx ;
case 13 : // relative horizontal line (h)
if ( toRel ) {
curx += x ;
} else {
x += curx ;
curx = x ;
}
addToD ( [ [ x ] ] ) ;
break ;
case 14 : // absolute vertical line (V)
y -= cury ;
case 15 : // relative vertical line (v)
if ( toRel ) {
cury += y ;
} else {
y += cury ;
cury = y ;
}
addToD ( [ [ y ] ] ) ;
break ;
case 2 : // absolute move (M)
case 4 : // absolute line (L)
case 18 : // absolute smooth quad (T)
x -= curx ;
y -= cury ;
case 5 : // relative line (l)
case 3 : // relative move (m)
// If the last segment was a "z", this must be relative to
if ( last _m && segList . getItem ( i - 1 ) . pathSegType === 1 && ! toRel ) {
curx = last _m [ 0 ] ;
cury = last _m [ 1 ] ;
2010-03-18 10:32:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
case 19 : // relative smooth quad (t)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ;
y += cury ;
curx = x ;
cury = y ;
}
if ( type === 3 ) last _m = [ curx , cury ] ;
2010-04-28 00:22:49 -05:00
2010-07-20 07:59:47 -05:00
addToD ( [ [ x , y ] ] ) ;
break ;
case 6 : // absolute cubic (C)
x -= curx ; x1 -= curx ; x2 -= curx ;
y -= cury ; y1 -= cury ; y2 -= cury ;
case 7 : // relative cubic (c)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ; x1 += curx ; x2 += curx ;
y += cury ; y1 += cury ; y2 += cury ;
curx = x ;
cury = y ;
}
addToD ( [ [ x1 , y1 ] , [ x2 , y2 ] , [ x , y ] ] ) ;
break ;
case 8 : // absolute quad (Q)
x -= curx ; x1 -= curx ;
y -= cury ; y1 -= cury ;
case 9 : // relative quad (q)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ; x1 += curx ;
y += cury ; y1 += cury ;
curx = x ;
cury = y ;
}
addToD ( [ [ x1 , y1 ] , [ x , y ] ] ) ;
break ;
case 10 : // absolute elliptical arc (A)
x -= curx ;
y -= cury ;
case 11 : // relative elliptical arc (a)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ;
y += cury ;
curx = x ;
cury = y ;
}
addToD ( [ [ seg . r1 , seg . r2 ] ] , [
seg . angle ,
( seg . largeArcFlag ? 1 : 0 ) ,
( seg . sweepFlag ? 1 : 0 )
] , [ x , y ]
) ;
break ;
case 16 : // absolute smooth cubic (S)
x -= curx ; x2 -= curx ;
y -= cury ; y2 -= cury ;
case 17 : // relative smooth cubic (s)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ; x2 += curx ;
y += cury ; y2 += cury ;
curx = x ;
cury = y ;
}
addToD ( [ [ x2 , y2 ] , [ x , y ] ] ) ;
break ;
} // switch on path segment type
} // for each segment
return d ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
} ( ) ;
pathActions . init ( ) ;
// Group: Serialization
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: removeUnusedDefElems
// Looks at DOM elements inside the <defs> to see if they are referred to,
// removes them from the DOM if they are not.
//
// Returns:
// The amount of elements that were removed
var removeUnusedDefElems = this . removeUnusedDefElems = function ( ) {
var defs = svgcontent . getElementsByTagNameNS ( svgns , "defs" ) ;
if ( ! defs || ! defs . length ) return 0 ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var defelem _uses = [ ] ,
numRemoved = 0 ;
var attrs = [ 'fill' , 'stroke' , 'filter' , 'marker-start' , 'marker-mid' , 'marker-end' ] ;
var alen = attrs . length ;
var all _els = svgcontent . getElementsByTagNameNS ( svgns , '*' ) ;
var all _len = all _els . length ;
for ( var i = 0 ; i < all _len ; i ++ ) {
var el = all _els [ i ] ;
for ( var j = 0 ; j < alen ; j ++ ) {
var ref = getUrlFromAttr ( el . getAttribute ( attrs [ j ] ) ) ;
if ( ref ) defelem _uses . push ( ref . substr ( 1 ) ) ;
}
// gradients can refer to other gradients
2010-09-10 15:19:23 -05:00
var href = getHref ( el ) ;
2010-07-20 07:59:47 -05:00
if ( href && href . indexOf ( '#' ) == 0 ) {
defelem _uses . push ( href . substr ( 1 ) ) ;
}
} ;
2010-09-10 15:19:23 -05:00
var defelems = $ ( svgcontent ) . find ( "linearGradient, radialGradient, filter, marker, svg" ) ;
2010-07-20 07:59:47 -05:00
defelem _ids = [ ] ,
i = defelems . length ;
while ( i -- ) {
var defelem = defelems [ i ] ;
var id = defelem . id ;
if ( $ . inArray ( id , defelem _uses ) == - 1 ) {
// Not found, so remove
defelem . parentNode . removeChild ( defelem ) ;
numRemoved ++ ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
// Remove defs if empty
var i = defs . length ;
while ( i -- ) {
var def = defs [ i ] ;
if ( ! def . getElementsByTagNameNS ( svgns , '*' ) . length ) {
def . parentNode . removeChild ( def ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
return numRemoved ;
}
// Function: svgCanvasToString
// Main function to set up the SVG content for output
//
// Returns:
// String containing the SVG image for output
var svgCanvasToString = this . svgCanvasToString = function ( ) {
// keep calling it until there are none to remove
while ( removeUnusedDefElems ( ) > 0 ) { } ;
pathActions . clear ( true ) ;
// Keep SVG-Edit comment on top
$ . each ( svgcontent . childNodes , function ( i , node ) {
if ( i && node . nodeType == 8 && node . data . indexOf ( 'Created with' ) != - 1 ) {
svgcontent . insertBefore ( node , svgcontent . firstChild ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
var naked _svgs = [ ] ;
// Unwrap gsvg if it has no special attributes (only id and style)
$ ( svgcontent ) . find ( 'g:data(gsvg)' ) . each ( function ( ) {
var attrs = this . attributes ;
var len = attrs . length ;
for ( var i = 0 ; i < len ; i ++ ) {
if ( attrs [ i ] . nodeName == 'id' || attrs [ i ] . nodeName == 'style' ) {
len -- ;
2010-03-18 10:32:47 -05:00
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// No significant attributes, so ungroup
if ( len <= 0 ) {
var svg = this . firstChild ;
naked _svgs . push ( svg ) ;
$ ( this ) . replaceWith ( svg ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
var output = svgToString ( svgcontent , 0 ) ;
// Rewrap gsvg
if ( naked _svgs . length ) {
$ ( naked _svgs ) . each ( function ( ) {
groupSvgElem ( this ) ;
} ) ;
}
return output ;
}
// Function: svgToString
// Sub function ran on each SVG element to convert it to a string as desired
//
// Parameters:
// elem - The SVG element to convert
// indent - Integer with the amount of spaces to indent this tag
//
// Returns:
// String with the given element as an SVG tag
var svgToString = this . svgToString = function ( elem , indent ) {
var out = new Array ( ) , toXml = Utils . toXml ;
if ( elem ) {
cleanupElement ( elem ) ;
var attrs = elem . attributes ,
attr ,
i ,
childs = elem . childNodes ;
for ( var i = 0 ; i < indent ; i ++ ) out . push ( " " ) ;
out . push ( "<" ) ; out . push ( elem . nodeName ) ;
if ( elem . id == 'svgcontent' ) {
// Process root element separately
var res = getResolution ( ) ;
out . push ( ' width="' + res . w + '" height="' + res . h + '" xmlns="' + svgns + '"' ) ;
var nsuris = { } ;
// Check elements for namespaces, add if found
$ ( elem ) . find ( '*' ) . andSelf ( ) . each ( function ( ) {
var el = this ;
$ . each ( this . attributes , function ( i , attr ) {
var uri = attr . namespaceURI ;
if ( uri && ! nsuris [ uri ] && nsMap [ uri ] !== 'xmlns' && nsMap [ uri ] !== 'xml' ) {
nsuris [ uri ] = true ;
out . push ( " xmlns:" + nsMap [ uri ] + '="' + uri + '"' ) ;
}
} ) ;
2010-02-05 21:36:35 -06:00
} ) ;
2010-07-20 07:59:47 -05:00
var i = attrs . length ;
while ( i -- ) {
attr = attrs . item ( i ) ;
var attrVal = toXml ( attr . nodeValue ) ;
// Namespaces have already been dealt with, so skip
if ( attr . nodeName . indexOf ( 'xmlns:' ) === 0 ) continue ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// only serialize attributes we don't use internally
if ( attrVal != "" &&
$ . inArray ( attr . localName , [ 'width' , 'height' , 'xmlns' , 'x' , 'y' , 'viewBox' , 'id' , 'overflow' ] ) == - 1 )
{
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( ! attr . namespaceURI || nsMap [ attr . namespaceURI ] ) {
out . push ( ' ' ) ;
out . push ( attr . nodeName ) ; out . push ( "=\"" ) ;
out . push ( attrVal ) ; out . push ( "\"" ) ;
}
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} else {
for ( var i = attrs . length - 1 ; i >= 0 ; i -- ) {
attr = attrs . item ( i ) ;
var attrVal = toXml ( attr . nodeValue ) ;
//remove bogus attributes added by Gecko
if ( $ . inArray ( attr . localName , [ '-moz-math-font-style' , '_moz-math-font-style' ] ) !== - 1 ) continue ;
if ( attrVal != "" ) {
if ( attrVal . indexOf ( 'pointer-events' ) == 0 ) continue ;
if ( attr . localName == "class" && attrVal . indexOf ( 'se_' ) == 0 ) continue ;
out . push ( " " ) ;
if ( attr . localName == 'd' ) attrVal = pathActions . convertPath ( elem , true ) ;
if ( ! isNaN ( attrVal ) ) {
attrVal = shortFloat ( attrVal ) ;
}
// Embed images when saving
if ( save _options . apply
&& elem . nodeName == 'image'
&& attr . localName == 'href'
&& save _options . images
&& save _options . images == 'embed' )
{
var img = encodableImages [ attrVal ] ;
if ( img ) attrVal = img ;
}
// map various namespaces to our fixed namespace prefixes
// (the default xmlns attribute itself does not get a prefix)
if ( ! attr . namespaceURI || attr . namespaceURI == svgns || nsMap [ attr . namespaceURI ] ) {
out . push ( attr . nodeName ) ; out . push ( "=\"" ) ;
out . push ( attrVal ) ; out . push ( "\"" ) ;
}
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( elem . hasChildNodes ( ) ) {
out . push ( ">" ) ;
indent ++ ;
var bOneLine = false ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
for ( var i = 0 ; i < childs . length ; i ++ )
{
var child = childs . item ( i ) ;
switch ( child . nodeType ) {
case 1 : // element node
out . push ( "\n" ) ;
out . push ( svgToString ( childs . item ( i ) , indent ) ) ;
break ;
case 3 : // text node
var str = child . nodeValue . replace ( /^\s+|\s+$/g , "" ) ;
if ( str != "" ) {
bOneLine = true ;
out . push ( toXml ( str ) + "" ) ;
}
break ;
case 8 : // comment
out . push ( "\n" ) ;
out . push ( new Array ( indent + 1 ) . join ( " " ) ) ;
out . push ( "<!--" ) ;
out . push ( child . data ) ;
out . push ( "-->" ) ;
break ;
} // switch on node type
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
indent -- ;
if ( ! bOneLine ) {
out . push ( "\n" ) ;
for ( var i = 0 ; i < indent ; i ++ ) out . push ( " " ) ;
}
out . push ( "</" ) ; out . push ( elem . nodeName ) ; out . push ( ">" ) ;
} else {
out . push ( "/>" ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
return out . join ( '' ) ;
} ; // end svgToString()
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: embedImage
// Converts a given image file to a data URL when possible, then runs a given callback
//
// Parameters:
// val - String with the path/URL of the image
// callback - Optional function to run when image data is found, supplies the
// result (data URL or false) as first parameter.
this . embedImage = function ( val , callback ) {
// load in the image and once it's loaded, get the dimensions
$ ( new Image ( ) ) . load ( function ( ) {
// create a canvas the same size as the raster image
var canvas = document . createElement ( "canvas" ) ;
canvas . width = this . width ;
canvas . height = this . height ;
// load the raster image into the canvas
canvas . getContext ( "2d" ) . drawImage ( this , 0 , 0 ) ;
// retrieve the data: URL
try {
var urldata = ';svgedit_url=' + encodeURIComponent ( val ) ;
urldata = canvas . toDataURL ( ) . replace ( ';base64' , urldata + ';base64' ) ;
encodableImages [ val ] = urldata ;
} catch ( e ) {
encodableImages [ val ] = false ;
}
last _good _img _url = val ;
if ( callback ) callback ( encodableImages [ val ] ) ;
} ) . attr ( 'src' , val ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setGoodImage
// Sets a given URL to be a "last good image" URL
this . setGoodImage = function ( val ) {
last _good _img _url = val ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
this . open = function ( ) {
// Nothing by default, handled by optional widget/extension
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: save
// Serializes the current drawing into SVG XML text and returns it to the 'saved' handler.
// This function also includes the XML prolog. Clients of the SvgCanvas bind their save
// function to the 'saved' event.
//
// Returns:
// Nothing
this . save = function ( opts ) {
// remove the selected outline before serializing
clearSelection ( ) ;
// Update save options if provided
if ( opts ) $ . extend ( save _options , opts ) ;
save _options . apply = true ;
// no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration
var str = svgCanvasToString ( ) ;
call ( "saved" , str ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: rasterExport
// Generates a PNG Data URL based on the current image, then calls "exported"
// with an object including the string and any issues found
this . rasterExport = function ( ) {
// remove the selected outline before serializing
clearSelection ( ) ;
// Check for known CanVG issues
var issues = [ ] ;
// Selector and notice
var issue _list = {
'feGaussianBlur' : uiStrings . exportNoBlur ,
'foreignObject' : uiStrings . exportNoforeignObject ,
'[stroke-dasharray]' : uiStrings . exportNoDashArray
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
var content = $ ( svgcontent ) ;
// Add font/text check if Canvas Text API is not implemented
if ( ! ( "font" in $ ( '<canvas>' ) [ 0 ] . getContext ( '2d' ) ) ) {
issue _list [ 'text' ] = uiStrings . exportNoText ;
}
$ . each ( issue _list , function ( sel , descr ) {
if ( content . find ( sel ) . length ) {
issues . push ( descr ) ;
}
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var str = svgCanvasToString ( ) ;
call ( "exported" , { svg : str , issues : issues } ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getSvgString
// Returns the current drawing as raw SVG XML text.
//
// Returns:
// The current drawing as raw SVG XML text.
this . getSvgString = function ( ) {
save _options . apply = false ;
return svgCanvasToString ( ) ;
} ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
//function randomizeIds
// This function determines whether to add a nonce to the prefix, when
// generating IDs in SVG-Edit
//
// Parameters:
// an opional boolean, which, if true, adds a nonce to the prefix. Thus
// svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true)
//
// if you're controlling SVG-Edit externally, and want randomized IDs, call
// this BEFORE calling svgCanvas.setSvgString
//
this . randomizeIds = function ( ) {
if ( arguments . length > 0 && arguments [ 0 ] == false ) {
randomize _ids = false ;
if ( extensions [ "Arrows" ] ) call ( "unsetarrownonce" ) ;
} else {
randomize _ids = true ;
if ( ! svgcontent . getAttributeNS ( se _ns , 'nonce' ) ) {
svgcontent . setAttributeNS ( se _ns , 'se:nonce' , nonce ) ;
if ( extensions [ "Arrows" ] ) call ( "setarrownonce" , nonce ) ;
}
}
}
// Function: uniquifyElems
// Ensure each element has a unique ID
//
// Parameters:
// g - The parent element of the tree to give unique IDs
var uniquifyElems = this . uniquifyElems = function ( g ) {
var ids = { } ;
walkTree ( g , function ( n ) {
// if it's an element node
if ( n . nodeType == 1 ) {
// and the element has an ID
if ( n . id ) {
// and we haven't tracked this ID yet
if ( ! ( n . id in ids ) ) {
// add this id to our map
ids [ n . id ] = { elem : null , attrs : [ ] , hrefs : [ ] } ;
}
ids [ n . id ] [ "elem" ] = n ;
}
// now search for all attributes on this element that might refer
// to other elements
$ . each ( [ "clip-path" , "fill" , "filter" , "marker-end" , "marker-mid" , "marker-start" , "mask" , "stroke" ] , function ( i , attr ) {
var attrnode = n . getAttributeNode ( attr ) ;
if ( attrnode ) {
// the incoming file has been sanitized, so we should be able to safely just strip off the leading #
var url = getUrlFromAttr ( attrnode . value ) ,
refid = url ? url . substr ( 1 ) : null ;
if ( refid ) {
if ( ! ( refid in ids ) ) {
// add this id to our map
ids [ refid ] = { elem : null , attrs : [ ] , hrefs : [ ] } ;
}
ids [ refid ] [ "attrs" ] . push ( attrnode ) ;
}
}
} ) ;
// check xlink:href now
2010-09-10 15:19:23 -05:00
var href = getHref ( n ) ;
2010-07-20 07:59:47 -05:00
// TODO: what if an <image> or <a> element refers to an element internally?
if ( href &&
$ . inArray ( n . nodeName , [ "filter" , "linearGradient" , "pattern" ,
"radialGradient" , "textPath" , "use" ] ) != - 1 )
{
var refid = href . substr ( 1 ) ;
if ( ! ( refid in ids ) ) {
// add this id to our map
ids [ refid ] = { elem : null , attrs : [ ] , hrefs : [ ] } ;
}
ids [ refid ] [ "hrefs" ] . push ( n ) ;
}
}
} ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
// in ids, we now have a map of ids, elements and attributes, let's re-identify
for ( var oldid in ids ) {
var elem = ids [ oldid ] [ "elem" ] ;
if ( elem ) {
var newid = getNextId ( ) ;
// manually increment obj_num because our cloned elements are not in the DOM yet
obj _num ++ ;
// assign element its new id
elem . id = newid ;
// remap all url() attributes
var attrs = ids [ oldid ] [ "attrs" ] ;
var j = attrs . length ;
while ( j -- ) {
var attr = attrs [ j ] ;
attr . ownerElement . setAttribute ( attr . name , "url(#" + newid + ")" ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// remap all href attributes
var hreffers = ids [ oldid ] [ "hrefs" ] ;
var k = hreffers . length ;
while ( k -- ) {
var hreffer = hreffers [ k ] ;
2010-09-10 15:19:23 -05:00
setHref ( hreffer , "#" + newid ) ;
2010-02-22 18:53:15 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// manually increment obj_num because our cloned elements are not in the DOM yet
obj _num ++ ;
}
2010-02-05 21:36:35 -06:00
2010-08-13 16:14:19 -05:00
// Function: convertToGroup
// Converts selected/given <use> or child SVG element to a group
var convertToGroup = this . convertToGroup = function ( elem ) {
if ( ! elem ) {
elem = selectedElements [ 0 ] ;
}
var $elem = $ ( elem ) ;
var batchCmd = new BatchCommand ( ) ;
var ts ;
if ( $elem . data ( 'gsvg' ) ) {
// Use the gsvg as the new group
var svg = elem . firstChild ;
var pt = $ ( svg ) . attr ( [ 'x' , 'y' ] ) ;
$ ( elem . firstChild . firstChild ) . unwrap ( ) ;
$ ( elem ) . removeData ( 'gsvg' ) ;
var tlist = getTransformList ( elem ) ;
var xform = svgroot . createSVGTransform ( ) ;
xform . setTranslate ( pt . x , pt . y ) ;
tlist . appendItem ( xform ) ;
recalculateDimensions ( elem ) ;
call ( "selected" , [ elem ] ) ;
} else if ( $elem . data ( 'symbol' ) ) {
elem = $elem . data ( 'symbol' ) ;
ts = $elem . attr ( 'transform' ) ;
var pos = $elem . attr ( [ 'x' , 'y' ] ) ;
// Not ideal, but works
ts += "translate(" + pos . x + "," + pos . y + ")" ;
var prev = $elem . prev ( ) ;
// Remove <use> element
batchCmd . addSubCommand ( new RemoveElementCommand ( $elem [ 0 ] , $elem [ 0 ] . parentNode ) ) ;
$elem . remove ( ) ;
// See if other elements reference this symbol
var has _more = $ ( svgcontent ) . find ( 'use:data(symbol)' ) . length ;
var g = svgdoc . createElementNS ( svgns , "g" ) ;
var childs = elem . childNodes ;
for ( var i = 0 ; i < childs . length ; i ++ ) {
g . appendChild ( childs [ i ] . cloneNode ( true ) ) ;
}
// while (elem.hasChildNodes())
// g.appendChild(elem.firstChild.cloneNode(true));
if ( ts )
g . setAttribute ( "transform" , ts ) ;
var parent = elem . parentNode ;
uniquifyElems ( g ) ;
// now give the g itself a new id
g . id = getNextId ( ) ;
prev . after ( g ) ;
if ( parent ) {
if ( ! has _more ) {
// remove symbol/svg element
parent . removeChild ( elem ) ;
batchCmd . addSubCommand ( new RemoveElementCommand ( elem , parent ) ) ;
}
batchCmd . addSubCommand ( new InsertElementCommand ( g ) ) ;
}
// recalculate dimensions on the top-level children so that unnecessary transforms
// are removed
walkTreePost ( g , function ( n ) { try { recalculateDimensions ( n ) } catch ( e ) { console . log ( e ) } } ) ;
2010-09-10 15:19:23 -05:00
// Give ID for any visible element missing one
$ ( g ) . find ( visElems ) . each ( function ( ) {
if ( ! this . id ) this . id = getNextId ( ) ;
} ) ;
2010-08-13 16:14:19 -05:00
clearSelection ( ) ;
addToSelection ( [ g ] ) ;
addCommandToHistory ( batchCmd ) ;
} else {
console . log ( 'Unexpected element to ungroup:' , elem ) ;
}
}
2010-07-20 07:59:47 -05:00
//
// Function: setSvgString
// This function sets the current drawing as the input SVG XML.
//
// Parameters:
// xmlString - The SVG as XML text.
//
// Returns:
// This function returns false if the set was unsuccessful, true otherwise.
this . setSvgString = function ( xmlString ) {
try {
// convert string into XML document
var newDoc = Utils . text2xml ( xmlString ) ;
// run it through our sanitizer to remove anything we do not support
sanitizeSvg ( newDoc . documentElement ) ;
var batchCmd = new BatchCommand ( "Change Source" ) ;
// remove old svg document
var oldzoom = svgroot . removeChild ( svgcontent ) ;
batchCmd . addSubCommand ( new RemoveElementCommand ( oldzoom , svgroot ) ) ;
// set new svg document
svgcontent = svgroot . appendChild ( svgdoc . importNode ( newDoc . documentElement , true ) ) ;
2010-09-10 15:19:23 -05:00
var content = $ ( svgcontent ) ;
2010-07-20 07:59:47 -05:00
// retrieve or set the nonce
n = svgcontent . getAttributeNS ( se _ns , 'nonce' ) ;
if ( n ) {
randomize _ids = true ;
nonce = n ;
if ( extensions [ "Arrows" ] ) call ( "setarrownonce" , n ) ;
} else if ( randomize _ids ) {
svgcontent . setAttributeNS ( xmlnsns , 'xmlns:se' , se _ns ) ;
svgcontent . setAttributeNS ( se _ns , 'se:nonce' , nonce ) ;
if ( extensions [ "Arrows" ] ) call ( "setarrownonce" , nonce ) ;
}
// change image href vals if possible
2010-09-10 15:19:23 -05:00
content . find ( 'image' ) . each ( function ( ) {
2010-07-20 07:59:47 -05:00
var image = this ;
preventClickDefault ( image ) ;
2010-09-10 15:19:23 -05:00
var val = getHref ( this ) ;
2010-07-20 07:59:47 -05:00
if ( val . indexOf ( 'data:' ) === 0 ) {
// Check if an SVG-edit data URI
var m = val . match ( /svgedit_url=(.*?);/ ) ;
if ( m ) {
var url = decodeURIComponent ( m [ 1 ] ) ;
$ ( new Image ( ) ) . load ( function ( ) {
image . setAttributeNS ( xlinkns , 'xlink:href' , url ) ;
} ) . attr ( 'src' , url ) ;
}
}
// Add to encodableImages if it loads
canvas . embedImage ( val ) ;
} ) ;
// Wrap child SVGs in group elements
2010-09-10 15:19:23 -05:00
content . find ( 'svg' ) . each ( function ( ) {
2010-08-13 16:14:19 -05:00
// Skip if it's in a <defs>
if ( $ ( this ) . closest ( 'defs' ) . length ) return ;
2010-07-20 07:59:47 -05:00
uniquifyElems ( this ) ;
// Check if it already has a gsvg group
var pa = this . parentNode ;
2010-08-13 16:14:19 -05:00
if ( pa . childNodes . length === 1 && pa . nodeName === 'g' ) {
2010-07-20 07:59:47 -05:00
$ ( pa ) . data ( 'gsvg' , this ) ;
pa . id = pa . id || getNextId ( ) ;
} else {
groupSvgElem ( this ) ;
}
} ) ;
2010-08-13 16:14:19 -05:00
// Set ref element for <use> elements
2010-09-10 15:19:23 -05:00
// TODO: This should also be done if the object is re-added through "redo"
content . find ( 'use' ) . each ( function ( ) {
var id = getHref ( this ) . substr ( 1 ) ;
2010-08-13 16:14:19 -05:00
var ref _elem = getElem ( id ) ;
$ ( this ) . data ( 'ref' , ref _elem ) ;
if ( ref _elem . tagName == 'symbol' || ref _elem . tagName == 'svg' ) {
$ ( this ) . data ( 'symbol' , ref _elem ) ;
}
} ) ;
2010-07-20 07:59:47 -05:00
// convert gradients with userSpaceOnUse to objectBoundingBox
2010-09-10 15:19:23 -05:00
content . find ( 'linearGradient, radialGradient' ) . each ( function ( ) {
2010-07-20 07:59:47 -05:00
var grad = this ;
if ( $ ( grad ) . attr ( 'gradientUnits' ) === 'userSpaceOnUse' ) {
// TODO: Support more than one element with this ref by duplicating parent grad
var elems = $ ( svgcontent ) . find ( '[fill=url(#' + grad . id + ')],[stroke=url(#' + grad . id + ')]' ) ;
if ( ! elems . length ) return ;
// get object's bounding box
var bb = elems [ 0 ] . getBBox ( ) ;
if ( grad . tagName === 'linearGradient' ) {
var g _coords = $ ( grad ) . attr ( [ 'x1' , 'y1' , 'x2' , 'y2' ] ) ;
$ ( grad ) . attr ( {
x1 : ( g _coords . x1 - bb . x ) / bb . width ,
y1 : ( g _coords . y1 - bb . y ) / bb . height ,
x2 : ( g _coords . x2 - bb . x ) / bb . width ,
y2 : ( g _coords . y1 - bb . y ) / bb . height
} ) ;
grad . removeAttribute ( 'gradientUnits' ) ;
} else {
// Note: radialGradient elements cannot be easily converted
// because userSpaceOnUse will keep circular gradients, while
// objectBoundingBox will x/y scale the gradient according to
// its bbox.
// For now we'll do nothing, though we should probably have
// the gradient be updated as the element is moved, as
// inkscape/illustrator do.
2010-04-01 23:56:21 -05:00
// var g_coords = $(grad).attr(['cx', 'cy', 'r']);
//
// $(grad).attr({
// cx: (g_coords.cx - bb.x) / bb.width,
// cy: (g_coords.cy - bb.y) / bb.height,
// r: g_coords.r
// });
//
// grad.removeAttribute('gradientUnits');
2010-07-20 07:59:47 -05:00
}
2010-03-18 10:32:47 -05:00
2010-02-18 10:12:44 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
// recalculate dimensions on the top-level children so that unnecessary transforms
// are removed
walkTreePost ( svgcontent , function ( n ) { try { recalculateDimensions ( n ) } catch ( e ) { console . log ( e ) } } ) ;
var attrs = {
id : 'svgcontent' ,
overflow : curConfig . show _outside _canvas ? 'visible' : 'hidden'
} ;
var percs = false ;
// determine proper size
if ( content . attr ( "viewBox" ) ) {
var vb = content . attr ( "viewBox" ) . split ( ' ' ) ;
attrs . width = vb [ 2 ] ;
attrs . height = vb [ 3 ] ;
}
// handle content that doesn't have a viewBox
else {
$ . each ( [ 'width' , 'height' ] , function ( i , dim ) {
// Set to 100 if not given
var val = content . attr ( dim ) ;
if ( ! val ) val = '100%' ;
if ( ( val + '' ) . substr ( - 1 ) === "%" ) {
// Use user units if percentage given
percs = true ;
} else {
attrs [ dim ] = convertToNum ( dim , val ) ;
}
} ) ;
}
2010-09-10 15:19:23 -05:00
// identify layers
identifyLayers ( ) ;
// Give ID for any visible layer children missing one
content . children ( ) . find ( visElems ) . each ( function ( ) {
if ( ! this . id ) this . id = getNextId ( ) ;
} ) ;
2010-07-20 07:59:47 -05:00
// Percentage width/height, so let's base it on visible elements
if ( percs ) {
var bb = getStrokedBBox ( ) ;
2010-09-10 15:19:23 -05:00
attrs . width = bb . width + bb . x ;
attrs . height = bb . height + bb . y ;
2010-07-20 07:59:47 -05:00
}
2010-09-10 15:19:23 -05:00
// Just in case negative numbers are given or
// result from the percs calculation
if ( attrs . width <= 0 ) attrs . width = 100 ;
if ( attrs . height <= 0 ) attrs . height = 100 ;
2010-07-20 07:59:47 -05:00
content . attr ( attrs ) ;
this . contentW = attrs [ 'width' ] ;
this . contentH = attrs [ 'height' ] ;
batchCmd . addSubCommand ( new InsertElementCommand ( svgcontent ) ) ;
// update root to the correct size
var changes = content . attr ( [ "width" , "height" ] ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( svgroot , changes ) ) ;
// reset zoom
current _zoom = 1 ;
// reset transform lists
svgTransformLists = { } ;
clearSelection ( ) ;
pathActions . clearData ( ) ;
svgroot . appendChild ( selectorManager . selectorParentGroup ) ;
addCommandToHistory ( batchCmd ) ;
call ( "changed" , [ svgcontent ] ) ;
} catch ( e ) {
console . log ( e ) ;
return false ;
}
return true ;
} ;
// Function: importSvgString
2010-08-13 16:14:19 -05:00
// This function imports the input SVG XML as a <symbol> in the <defs>, then adds a
// <use> to the current layer.
2010-07-20 07:59:47 -05:00
//
// Parameters:
// xmlString - The SVG as XML text.
//
// Returns:
// This function returns false if the import was unsuccessful, true otherwise.
// TODO:
// * properly handle if namespace is introduced by imported content (must add to svgcontent
// and update all prefixes in the imported node)
// * properly handle recalculating dimensions, recalculateDimensions() doesn't handle
// arbitrary transform lists, but makes some assumptions about how the transform list
// was obtained
// * import should happen in top-left of current zoomed viewport
2010-08-13 16:14:19 -05:00
this . importSvgString = function ( xmlString ) {
2010-07-20 07:59:47 -05:00
try {
// convert string into XML document
var newDoc = Utils . text2xml ( xmlString ) ;
// run it through our sanitizer to remove anything we do not support
sanitizeSvg ( newDoc . documentElement ) ;
var batchCmd = new BatchCommand ( "Change Source" ) ;
// import new svg document into our document
2010-08-13 16:14:19 -05:00
var svg = svgdoc . importNode ( newDoc . documentElement , true ) ;
2010-09-10 15:19:23 -05:00
2010-08-13 16:14:19 -05:00
var innerw = convertToNum ( 'width' , svg . getAttribute ( "width" ) ) ,
innerh = convertToNum ( 'height' , svg . getAttribute ( "height" ) ) ,
innervb = svg . getAttribute ( "viewBox" ) ,
2010-07-20 07:59:47 -05:00
// if no explicit viewbox, create one out of the width and height
vb = innervb ? innervb . split ( " " ) : [ 0 , 0 , innerw , innerh ] ;
for ( var j = 0 ; j < 4 ; ++ j )
vb [ j ] = Number ( vb [ j ] ) ;
// TODO: properly handle preserveAspectRatio
var canvasw = Number ( svgcontent . getAttribute ( "width" ) ) ,
canvash = Number ( svgcontent . getAttribute ( "height" ) ) ;
// imported content should be 1/3 of the canvas on its largest dimension
if ( innerh > innerw ) {
var ts = "scale(" + ( canvash / 3 ) / vb [ 3 ] + ")" ;
}
else {
var ts = "scale(" + ( canvash / 3 ) / vb [ 2 ] + ")" ;
}
// Hack to make recalculateDimensions understand how to scale
ts = "translate(0) " + ts + " translate(0)" ;
2010-08-13 16:14:19 -05:00
// Uncomment this once Firefox has fixed their symbol bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=353575
// var symbol = svgdoc.createElementNS(svgns, "symbol");
// while (svg.firstChild) {
// symbol.appendChild(svg.firstChild);
// }
// var attrs = svg.attributes;
// for(var i=0; i < attrs.length; i++) {
// var attr = attrs[i];
// symbol.setAttribute(attr.nodeName, attr.nodeValue);
// }
var symbol = svg ;
symbol . id = getNextId ( ) ;
var use _el = svgdoc . createElementNS ( svgns , "use" ) ;
2010-09-10 15:19:23 -05:00
setHref ( use _el , "#" + symbol . id ) ;
2010-08-13 16:14:19 -05:00
findDefs ( ) . appendChild ( symbol ) ;
current _layer . appendChild ( use _el ) ;
2010-09-10 15:19:23 -05:00
use _el . id = getNextId ( ) ;
2010-08-13 16:14:19 -05:00
clearSelection ( ) ;
use _el . setAttribute ( "transform" , ts ) ;
recalculateDimensions ( use _el ) ;
$ ( use _el ) . data ( 'symbol' , symbol ) ;
addToSelection ( [ use _el ] ) ;
return true ;
2010-07-20 07:59:47 -05:00
// TODO: Find way to add this in a recalculateDimensions-parsable way
2010-06-03 07:13:37 -05:00
// if (vb[0] != 0 || vb[1] != 0)
// ts = "translate(" + (-vb[0]) + "," + (-vb[1]) + ") " + ts;
2010-02-22 18:53:15 -06:00
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
} catch ( e ) {
console . log ( e ) ;
return false ;
}
2010-02-20 23:54:20 -06:00
2010-07-20 07:59:47 -05:00
return true ;
} ;
// Layer API Functions
// Group: Layers
// Function: identifyLayers
// Updates layer system
var identifyLayers = function ( ) {
all _layers = [ ] ;
var numchildren = svgcontent . childNodes . length ;
// loop through all children of svgcontent
var orphans = [ ] , layernames = [ ] ;
for ( var i = 0 ; i < numchildren ; ++ i ) {
var child = svgcontent . childNodes . item ( i ) ;
// for each g, find its layer name
if ( child && child . nodeType == 1 ) {
if ( child . tagName == "g" ) {
var name = $ ( "title" , child ) . text ( ) ;
// Hack for Opera 10.60
if ( ! name && isOpera && child . querySelectorAll ) {
name = $ ( child . querySelectorAll ( 'title' ) ) . text ( ) ;
}
// store layer and name in global variable
if ( name ) {
layernames . push ( name ) ;
all _layers . push ( [ name , child ] ) ;
current _layer = child ;
walkTree ( child , function ( e ) { e . setAttribute ( "style" , "pointer-events:inherit" ) ; } ) ;
current _layer . setAttribute ( "style" , "pointer-events:none" ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// if group did not have a name, it is an orphan
else {
2010-02-05 21:36:35 -06:00
orphans . push ( child ) ;
}
}
2010-07-20 07:59:47 -05:00
// if child has a bbox (i.e. not a <title> or <defs> element), then it is an orphan
else if ( getBBox ( child ) && child . nodeName != 'defs' ) { // Opera returns a BBox for defs
var bb = getBBox ( child ) ;
orphans . push ( child ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
// create a new layer and add all the orphans to it
if ( orphans . length > 0 ) {
var i = 1 ;
while ( $ . inArray ( ( "Layer " + i ) , layernames ) != - 1 ) { i ++ ; }
var newname = "Layer " + i ;
current _layer = svgdoc . createElementNS ( svgns , "g" ) ;
2010-02-05 21:36:35 -06:00
var layer _title = svgdoc . createElementNS ( svgns , "title" ) ;
2010-07-20 07:59:47 -05:00
layer _title . textContent = newname ;
current _layer . appendChild ( layer _title ) ;
for ( var j = 0 ; j < orphans . length ; ++ j ) {
current _layer . appendChild ( orphans [ j ] ) ;
}
current _layer = svgcontent . appendChild ( current _layer ) ;
all _layers . push ( [ newname , current _layer ] ) ;
}
walkTree ( current _layer , function ( e ) { e . setAttribute ( "style" , "pointer-events:inherit" ) ; } ) ;
current _layer . setAttribute ( "style" , "pointer-events:all" ) ;
} ;
// Function: createLayer
// Creates a new top-level layer in the drawing with the given name, sets the current layer
// to it, and then clears the selection This function then calls the 'changed' handler.
// This is an undoable action.
//
// Parameters:
// name - The given name
this . createLayer = function ( name ) {
var batchCmd = new BatchCommand ( "Create Layer" ) ;
var new _layer = svgdoc . createElementNS ( svgns , "g" ) ;
var layer _title = svgdoc . createElementNS ( svgns , "title" ) ;
layer _title . textContent = name ;
new _layer . appendChild ( layer _title ) ;
new _layer = svgcontent . appendChild ( new _layer ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( new _layer ) ) ;
addCommandToHistory ( batchCmd ) ;
clearSelection ( ) ;
identifyLayers ( ) ;
canvas . setCurrentLayer ( name ) ;
call ( "changed" , [ new _layer ] ) ;
} ;
2010-09-10 15:19:23 -05:00
// Function: cloneLayer
// Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents
// to it, and then clears the selection This function then calls the 'changed' handler.
// This is an undoable action.
//
// Parameters:
// name - The given name
this . cloneLayer = function ( name ) {
var batchCmd = new BatchCommand ( "Duplicate Layer" ) ;
var new _layer = svgdoc . createElementNS ( svgns , "g" ) ;
var layer _title = svgdoc . createElementNS ( svgns , "title" ) ;
layer _title . textContent = name ;
new _layer . appendChild ( layer _title ) ;
$ ( current _layer ) . after ( new _layer ) ;
var childs = current _layer . childNodes ;
for ( var i = 0 ; i < childs . length ; i ++ ) {
var ch = childs [ i ] ;
if ( ch . localName == 'title' ) continue ;
new _layer . appendChild ( copyElem ( ch ) ) ;
}
clearSelection ( ) ;
identifyLayers ( ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( new _layer ) ) ;
addCommandToHistory ( batchCmd ) ;
canvas . setCurrentLayer ( name ) ;
call ( "changed" , [ new _layer ] ) ;
} ;
2010-07-20 07:59:47 -05:00
// Function: deleteCurrentLayer
// Deletes the current layer from the drawing and then clears the selection. This function
// then calls the 'changed' handler. This is an undoable action.
this . deleteCurrentLayer = function ( ) {
if ( current _layer && all _layers . length > 1 ) {
var batchCmd = new BatchCommand ( "Delete Layer" ) ;
// actually delete from the DOM and store in our Undo History
var parent = current _layer . parentNode ;
batchCmd . addSubCommand ( new RemoveElementCommand ( current _layer , parent ) ) ;
parent . removeChild ( current _layer ) ;
2010-02-05 21:36:35 -06:00
addCommandToHistory ( batchCmd ) ;
2010-07-20 07:59:47 -05:00
clearSelection ( ) ;
2010-02-05 21:36:35 -06:00
identifyLayers ( ) ;
2010-07-20 07:59:47 -05:00
canvas . setCurrentLayer ( all _layers [ all _layers . length - 1 ] [ 0 ] ) ;
call ( "changed" , [ svgcontent ] ) ;
return true ;
}
return false ;
} ;
2010-09-10 15:19:23 -05:00
// Function: hasLayer
// Check if layer with given name already exists
this . hasLayer = function ( name ) {
for ( var i = 0 ; i < all _layers . length ; i ++ ) {
if ( all _layers [ i ] [ 0 ] == name ) return true ;
}
return false ;
} ;
2010-07-20 07:59:47 -05:00
// Function: getNumLayers
// Returns the number of layers in the current drawing.
//
// Returns:
// The number of layers in the current drawing.
this . getNumLayers = function ( ) {
return all _layers . length ;
} ;
// Function: getLayer
// Returns the name of the ith layer. If the index is out of range, an empty string is returned.
//
// Parameters:
// i - the zero-based index of the layer you are querying.
//
// Returns:
// The name of the ith layer
this . getLayer = function ( i ) {
if ( i >= 0 && i < canvas . getNumLayers ( ) ) {
return all _layers [ i ] [ 0 ] ;
}
return "" ;
} ;
// Function: getCurrentLayer
// Returns the name of the currently selected layer. If an error occurs, an empty string
// is returned.
//
// Returns:
// The name of the currently active layer.
this . getCurrentLayer = function ( ) {
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 1 ] == current _layer ) {
2010-02-05 21:36:35 -06:00
return all _layers [ i ] [ 0 ] ;
}
2010-07-20 07:59:47 -05:00
}
return "" ;
} ;
// Function: setCurrentLayer
// Sets the current layer. If the name is not a valid layer name, then this function returns
// false. Otherwise it returns true. This is not an undo-able action.
//
// Parameters:
// name - the name of the layer you want to switch to.
//
// Returns:
// true if the current layer was switched, otherwise false
this . setCurrentLayer = function ( name ) {
name = Utils . toXml ( name ) ;
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( name == all _layers [ i ] [ 0 ] ) {
if ( current _layer != all _layers [ i ] [ 1 ] ) {
clearSelection ( ) ;
current _layer . setAttribute ( "style" , "pointer-events:none" ) ;
current _layer = all _layers [ i ] [ 1 ] ;
current _layer . setAttribute ( "style" , "pointer-events:all" ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return true ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
return false ;
} ;
// Function: renameCurrentLayer
// Renames the current layer. If the layer name is not valid (i.e. unique), then this function
// does nothing and returns false, otherwise it returns true. This is an undo-able action.
//
// Parameters:
// newname - the new name you want to give the current layer. This name must be unique
// among all layer names.
//
// Returns:
// true if the rename succeeded, false otherwise.
this . renameCurrentLayer = function ( newname ) {
if ( current _layer ) {
var oldLayer = current _layer ;
// setCurrentLayer will return false if the name doesn't already exists
if ( ! canvas . setCurrentLayer ( newname ) ) {
var batchCmd = new BatchCommand ( "Rename Layer" ) ;
// find the index of the layer
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 1 ] == oldLayer ) break ;
}
var oldname = all _layers [ i ] [ 0 ] ;
all _layers [ i ] [ 0 ] = Utils . toXml ( newname ) ;
// now change the underlying title element contents
var len = oldLayer . childNodes . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var child = oldLayer . childNodes . item ( i ) ;
// found the <title> element, now append all the
if ( child && child . tagName == "title" ) {
// wipe out old name
while ( child . firstChild ) { child . removeChild ( child . firstChild ) ; }
child . textContent = newname ;
batchCmd . addSubCommand ( new ChangeElementCommand ( child , { "#text" : oldname } ) ) ;
addCommandToHistory ( batchCmd ) ;
call ( "changed" , [ oldLayer ] ) ;
return true ;
}
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
current _layer = oldLayer ;
}
return false ;
} ;
// Function: setCurrentLayerPosition
// Changes the position of the current layer to the new value. If the new index is not valid,
// this function does nothing and returns false, otherwise it returns true. This is an
// undo-able action.
//
// Parameters:
// newpos - The zero-based index of the new position of the layer. This should be between
// 0 and (number of layers - 1)
//
// Returns:
// true if the current layer position was changed, false otherwise.
this . setCurrentLayerPosition = function ( newpos ) {
if ( current _layer && newpos >= 0 && newpos < all _layers . length ) {
for ( var oldpos = 0 ; oldpos < all _layers . length ; ++ oldpos ) {
if ( all _layers [ oldpos ] [ 1 ] == current _layer ) break ;
}
// some unknown error condition (current_layer not in all_layers)
if ( oldpos == all _layers . length ) { return false ; }
if ( oldpos != newpos ) {
// if our new position is below us, we need to insert before the node after newpos
var refLayer = null ;
var oldNextSibling = current _layer . nextSibling ;
if ( newpos > oldpos ) {
if ( newpos < all _layers . length - 1 ) {
refLayer = all _layers [ newpos + 1 ] [ 1 ] ;
}
}
// if our new position is above us, we need to insert before the node at newpos
else {
refLayer = all _layers [ newpos ] [ 1 ] ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
svgcontent . insertBefore ( current _layer , refLayer ) ;
addCommandToHistory ( new MoveElementCommand ( current _layer , oldNextSibling , svgcontent ) ) ;
identifyLayers ( ) ;
canvas . setCurrentLayer ( all _layers [ newpos ] [ 0 ] ) ;
return true ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
return false ;
} ;
// Function: getLayerVisibility
// Returns whether the layer is visible. If the layer name is not valid, then this function
// returns false.
//
// Parameters:
// layername - the name of the layer which you want to query.
//
// Returns:
// The visibility state of the layer, or false if the layer name was invalid.
this . getLayerVisibility = function ( layername ) {
// find the layer
var layer = null ;
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 0 ] == layername ) {
layer = all _layers [ i ] [ 1 ] ;
break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
if ( ! layer ) return false ;
return ( layer . getAttribute ( "display" ) != "none" ) ;
} ;
// Function: setLayerVisibility
// Sets the visibility of the layer. If the layer name is not valid, this function return
// false, otherwise it returns true. This is an undo-able action.
//
// Parameters:
// layername - the name of the layer to change the visibility
// bVisible - true/false, whether the layer should be visible
//
// Returns:
// true if the layer's visibility was set, false otherwise
this . setLayerVisibility = function ( layername , bVisible ) {
// find the layer
var layer = null ;
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 0 ] == layername ) {
layer = all _layers [ i ] [ 1 ] ;
break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
if ( ! layer ) return false ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var oldDisplay = layer . getAttribute ( "display" ) ;
if ( ! oldDisplay ) oldDisplay = "inline" ;
layer . setAttribute ( "display" , bVisible ? "inline" : "none" ) ;
addCommandToHistory ( new ChangeElementCommand ( layer , { "display" : oldDisplay } , "Layer Visibility" ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( layer == current _layer ) {
clearSelection ( ) ;
pathActions . clear ( ) ;
}
// call("changed", [selected]);
return true ;
} ;
// Function: moveSelectedToLayer
// Moves the selected elements to layername. If the name is not a valid layer name, then false
// is returned. Otherwise it returns true. This is an undo-able action.
//
// Parameters:
// layername - the name of the layer you want to which you want to move the selected elements
//
// Returns:
// true if the selected elements were moved to the layer, false otherwise.
this . moveSelectedToLayer = function ( layername ) {
// find the layer
var layer = null ;
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 0 ] == layername ) {
layer = all _layers [ i ] [ 1 ] ;
break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
if ( ! layer ) return false ;
var batchCmd = new BatchCommand ( "Move Elements to Layer" ) ;
// loop for each selected element and move it
var selElems = selectedElements ;
var i = selElems . length ;
while ( i -- ) {
var elem = selElems [ i ] ;
if ( ! elem ) continue ;
var oldNextSibling = elem . nextSibling ;
// TODO: this is pretty brittle!
var oldLayer = elem . parentNode ;
layer . appendChild ( elem ) ;
batchCmd . addSubCommand ( new MoveElementCommand ( elem , oldNextSibling , oldLayer ) ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
addCommandToHistory ( batchCmd ) ;
return true ;
} ;
2010-09-10 15:19:23 -05:00
this . mergeLayer = function ( skipHistory ) {
var batchCmd = new BatchCommand ( "Merge Layer" ) ;
var prev = $ ( current _layer ) . prev ( ) [ 0 ] ;
if ( ! prev ) return ;
var childs = current _layer . childNodes ;
var len = childs . length ;
batchCmd . addSubCommand ( new RemoveElementCommand ( current _layer , svgcontent ) ) ;
while ( current _layer . firstChild ) {
var ch = current _layer . firstChild ;
if ( ch . localName == 'title' ) {
batchCmd . addSubCommand ( new RemoveElementCommand ( ch , current _layer ) ) ;
current _layer . removeChild ( ch ) ;
continue ;
}
var oldNextSibling = ch . nextSibling ;
prev . appendChild ( ch ) ;
batchCmd . addSubCommand ( new MoveElementCommand ( ch , oldNextSibling , current _layer ) ) ;
}
// Remove current layer
svgcontent . removeChild ( current _layer ) ;
if ( ! skipHistory ) {
clearSelection ( ) ;
identifyLayers ( ) ;
call ( "changed" , [ svgcontent ] ) ;
addCommandToHistory ( batchCmd ) ;
}
current _layer = prev ;
return batchCmd ;
}
this . mergeAllLayers = function ( ) {
var batchCmd = new BatchCommand ( "Merge all Layers" ) ;
current _layer = all _layers [ all _layers . length - 1 ] [ 1 ] ;
while ( $ ( svgcontent ) . children ( 'g' ) . length > 1 ) {
batchCmd . addSubCommand ( canvas . mergeLayer ( true ) ) ;
}
clearSelection ( ) ;
identifyLayers ( ) ;
call ( "changed" , [ svgcontent ] ) ;
addCommandToHistory ( batchCmd ) ;
}
2010-07-20 07:59:47 -05:00
// Function: getLayerOpacity
// Returns the opacity of the given layer. If the input name is not a layer, null is returned.
//
// Parameters:
// layername - name of the layer on which to get the opacity
//
// Returns:
// The opacity value of the given layer. This will be a value between 0.0 and 1.0, or null
// if layername is not a valid layer
this . getLayerOpacity = function ( layername ) {
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 0 ] == layername ) {
var g = all _layers [ i ] [ 1 ] ;
var opacity = g . getAttribute ( "opacity" ) ;
if ( ! opacity ) {
opacity = "1.0" ;
}
return parseFloat ( opacity ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
return null ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setLayerOpacity
// Sets the opacity of the given layer. If the input name is not a layer, nothing happens.
// This is not an undo-able action. NOTE: this function exists solely to apply
// a highlighting/de-emphasis effect to a layer, when it is possible for a user to affect
// the opacity of a layer, we will need to allow this function to produce an undo-able action.
// If opacity is not a value between 0.0 and 1.0, then nothing happens.
//
// Parameters:
// layername - name of the layer on which to set the opacity
// opacity - a float value in the range 0.0-1.0
this . setLayerOpacity = function ( layername , opacity ) {
if ( opacity < 0.0 || opacity > 1.0 ) return ;
for ( var i = 0 ; i < all _layers . length ; ++ i ) {
if ( all _layers [ i ] [ 0 ] == layername ) {
var g = all _layers [ i ] [ 1 ] ;
g . setAttribute ( "opacity" , opacity ) ;
break ;
}
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Group: Document functions
// Function: clear
// Clears the current document. This is not an undoable action.
this . clear = function ( ) {
pathActions . clear ( ) ;
// clear the svgcontent node
var nodes = svgcontent . childNodes ;
var len = svgcontent . childNodes . length ;
var i = 0 ;
clearSelection ( ) ;
for ( var rep = 0 ; rep < len ; rep ++ ) {
if ( nodes [ i ] . nodeType == 1 ) { // element node
svgcontent . removeChild ( nodes [ i ] ) ;
} else {
i ++ ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
// create empty first layer
all _layers = [ ] ;
canvas . createLayer ( "Layer 1" ) ;
// clear the undo stack
resetUndoStack ( ) ;
// reset the selector manager
selectorManager . initGroup ( ) ;
// reset the rubber band box
rubberBox = selectorManager . getRubberBandBox ( ) ;
call ( "cleared" ) ;
} ;
// Function: linkControlPoints
// Alias function
this . linkControlPoints = pathActions . linkControlPoints ;
// Function: getContentElem
// Returns the content DOM element
this . getContentElem = function ( ) { return svgcontent ; } ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getRootElem
// Returns the root DOM element
this . getRootElem = function ( ) { return svgroot ; } ;
2010-03-12 03:50:23 -06:00
2010-07-20 07:59:47 -05:00
// Function: getSelectedElems
// Returns the array with selected DOM elements
this . getSelectedElems = function ( ) { return selectedElements ; } ;
// Function: getResolution
// Returns the current dimensions and zoom level in an object
var getResolution = this . getResolution = function ( ) {
2010-02-05 21:36:35 -06:00
// var vb = svgcontent.getAttribute("viewBox").split(' ');
// return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom};
2010-07-20 07:59:47 -05:00
return {
'w' : svgcontent . getAttribute ( "width" ) / current _zoom ,
'h' : svgcontent . getAttribute ( "height" ) / current _zoom ,
'zoom' : current _zoom
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
} ;
// Function: getZoom
// Returns the current zoom level
this . getZoom = function ( ) { return current _zoom ; } ;
// Function: getVersion
// Returns a string which describes the revision number of SvgCanvas.
this . getVersion = function ( ) {
2010-09-13 17:05:37 -05:00
return "svgcanvas.js ($Rev: 1714 $)" ;
2010-07-20 07:59:47 -05:00
} ;
// Function: setUiStrings
// Update interface strings with given values
//
// Parameters:
// strs - Object with strings (see uiStrings for examples)
this . setUiStrings = function ( strs ) {
$ . extend ( uiStrings , strs ) ;
}
// Function: setConfig
// Update configuration options with given values
//
// Parameters:
// opts - Object with options (see curConfig for examples)
this . setConfig = function ( opts ) {
$ . extend ( curConfig , opts ) ;
}
// Function: getDocumentTitle
2010-08-13 16:14:19 -05:00
// Returns the current group/SVG's title contents
this . getTitle = function ( elem ) {
elem = elem || selectedElements [ 0 ] ;
if ( ! elem ) return ;
elem = $ ( elem ) . data ( 'gsvg' ) || $ ( elem ) . data ( 'symbol' ) || elem ;
var childs = elem . childNodes ;
2010-07-20 07:59:47 -05:00
for ( var i = 0 ; i < childs . length ; i ++ ) {
if ( childs [ i ] . nodeName == 'title' ) {
return childs [ i ] . textContent ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
return '' ;
}
2010-09-10 15:19:23 -05:00
// Function: getHref
// Returns the given element's xlink:href value
var getHref = this . getHref = function ( elem ) {
return elem . getAttributeNS ( xlinkns , "href" ) ;
}
// Function: setHref
// Sets the given element's xlink:href value
var setHref = this . setHref = function ( elem , val ) {
elem . setAttributeNS ( xlinkns , "xlink:href" , val ) ;
}
2010-08-13 16:14:19 -05:00
// Function: setGroupTitle
// Sets the group/SVG's title content
// TODO: Combine this with setDocumentTitle
this . setGroupTitle = function ( val ) {
var elem = selectedElements [ 0 ] ;
elem = $ ( elem ) . data ( 'gsvg' ) || elem ;
var ts = $ ( elem ) . children ( 'title' ) ;
var batchCmd = new BatchCommand ( "Set Label" ) ;
if ( ! val . length ) {
// Remove title element
batchCmd . addSubCommand ( new RemoveElementCommand ( ts [ 0 ] , elem ) ) ;
ts . remove ( ) ;
} else if ( ts . length ) {
// Change title contents
var title = ts [ 0 ] ;
batchCmd . addSubCommand ( new ChangeElementCommand ( title , { '#text' : title . textContent } ) ) ;
title . textContent = val ;
} else {
// Add title element
title = svgdoc . createElementNS ( svgns , "title" ) ;
title . textContent = val ;
$ ( elem ) . prepend ( title ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( title ) ) ;
}
addCommandToHistory ( batchCmd ) ;
}
// Function: getDocumentTitle
// Returns the current document title or an empty string if not found
this . getDocumentTitle = function ( ) {
return canvas . getTitle ( svgcontent ) ;
}
2010-07-20 07:59:47 -05:00
// Function: setDocumentTitle
// Adds/updates a title element for the document with the given name.
// This is an undoable action
//
// Parameters:
// newtitle - String with the new title
this . setDocumentTitle = function ( newtitle ) {
var childs = svgcontent . childNodes , doc _title = false , old _title = '' ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var batchCmd = new BatchCommand ( "Change Image Title" ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
for ( var i = 0 ; i < childs . length ; i ++ ) {
if ( childs [ i ] . nodeName == 'title' ) {
doc _title = childs [ i ] ;
old _title = doc _title . textContent ;
break ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
if ( ! doc _title ) {
doc _title = svgdoc . createElementNS ( svgns , "title" ) ;
svgcontent . insertBefore ( doc _title , svgcontent . firstChild ) ;
}
if ( newtitle . length ) {
doc _title . textContent = newtitle ;
} else {
// No title given, so element is not necessary
doc _title . parentNode . removeChild ( doc _title ) ;
}
batchCmd . addSubCommand ( new ChangeElementCommand ( doc _title , { '#text' : old _title } ) ) ;
addCommandToHistory ( batchCmd ) ;
}
// Function: getEditorNS
// Returns the editor's namespace URL, optionally adds it to root element
//
// Parameters:
// add - Boolean to indicate whether or not to add the namespace value
this . getEditorNS = function ( add ) {
if ( add ) {
svgcontent . setAttribute ( 'xmlns:se' , se _ns ) ;
}
return se _ns ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setResolution
// Changes the document's dimensions to the given size
//
// Parameters:
// x - Number with the width of the new dimensions in user units.
// Can also be the string "fit" to indicate "fit to content"
// y - Number with the height of the new dimensions in user units.
//
// Returns:
// Boolean to indicate if resolution change was succesful.
// It will fail on "fit to content" option with no content to fit to.
this . setResolution = function ( x , y ) {
var res = getResolution ( ) ;
var w = res . w , h = res . h ;
var batchCmd ;
if ( x == 'fit' ) {
// Get bounding box
var bbox = getStrokedBBox ( ) ;
if ( bbox ) {
batchCmd = new BatchCommand ( "Fit Canvas to Content" ) ;
var visEls = getVisibleElements ( ) ;
addToSelection ( visEls ) ;
var dx = [ ] , dy = [ ] ;
$ . each ( visEls , function ( i , item ) {
dx . push ( bbox . x * - 1 ) ;
dy . push ( bbox . y * - 1 ) ;
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var cmd = canvas . moveSelectedElements ( dx , dy , true ) ;
batchCmd . addSubCommand ( cmd ) ;
clearSelection ( ) ;
2010-02-10 15:41:47 -06:00
2010-07-20 07:59:47 -05:00
x = Math . round ( bbox . width ) ;
y = Math . round ( bbox . height ) ;
} else {
return false ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
if ( x != w || y != h ) {
var handle = svgroot . suspendRedraw ( 1000 ) ;
if ( ! batchCmd ) {
batchCmd = new BatchCommand ( "Change Image Dimensions" ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
x = convertToNum ( 'width' , x ) ;
y = convertToNum ( 'height' , y ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
svgcontent . setAttribute ( 'width' , x ) ;
svgcontent . setAttribute ( 'height' , y ) ;
this . contentW = x ;
this . contentH = y ;
batchCmd . addSubCommand ( new ChangeElementCommand ( svgcontent , { "width" : w , "height" : h } ) ) ;
svgcontent . setAttribute ( "viewBox" , [ 0 , 0 , x / current _zoom , y / current _zoom ] . join ( ' ' ) ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( svgcontent , { "viewBox" : [ "0 0" , w , h ] . join ( ' ' ) } ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
addCommandToHistory ( batchCmd ) ;
svgroot . unsuspendRedraw ( handle ) ;
call ( "changed" , [ svgcontent ] ) ;
}
return true ;
} ;
// Function: getOffset
// Returns an object with x, y values indicating the svgcontent element's
// position in the editor's canvas.
this . getOffset = function ( ) {
return $ ( svgcontent ) . attr ( [ 'x' , 'y' ] ) ;
}
// Function: setBBoxZoom
// Sets the zoom level on the canvas-side based on the given value
//
// Parameters:
// val - Bounding box object to zoom to or string indicating zoom option
// editor_w - Integer with the editor's workarea box's width
// editor_h - Integer with the editor's workarea box's height
this . setBBoxZoom = function ( val , editor _w , editor _h ) {
var spacer = . 85 ;
var bb ;
var calcZoom = function ( bb ) {
if ( ! bb ) return false ;
var w _zoom = Math . round ( ( editor _w / bb . width ) * 100 * spacer ) / 100 ;
var h _zoom = Math . round ( ( editor _h / bb . height ) * 100 * spacer ) / 100 ;
var zoomlevel = Math . min ( w _zoom , h _zoom ) ;
canvas . setZoom ( zoomlevel ) ;
return { 'zoom' : zoomlevel , 'bbox' : bb } ;
}
if ( typeof val == 'object' ) {
bb = val ;
if ( bb . width == 0 || bb . height == 0 ) {
var newzoom = bb . zoom ? bb . zoom : current _zoom * bb . factor ;
canvas . setZoom ( newzoom ) ;
return { 'zoom' : current _zoom , 'bbox' : bb } ;
2010-02-05 21:36:35 -06:00
}
return calcZoom ( bb ) ;
}
2010-07-20 07:59:47 -05:00
switch ( val ) {
case 'selection' :
if ( ! selectedElements [ 0 ] ) return ;
var sel _elems = $ . map ( selectedElements , function ( n ) { if ( n ) return n ; } ) ;
bb = getStrokedBBox ( sel _elems ) ;
break ;
case 'canvas' :
var res = getResolution ( ) ;
spacer = . 95 ;
bb = { width : res . w , height : res . h , x : 0 , y : 0 } ;
break ;
case 'content' :
bb = getStrokedBBox ( ) ;
break ;
case 'layer' :
bb = getStrokedBBox ( getVisibleElements ( current _layer ) ) ;
break ;
default :
return ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
return calcZoom ( bb ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setZoom
// Sets the zoom to the given level
//
// Parameters:
// zoomlevel - Float indicating the zoom level to change to
this . setZoom = function ( zoomlevel ) {
var res = getResolution ( ) ;
svgcontent . setAttribute ( "viewBox" , "0 0 " + res . w / zoomlevel + " " + res . h / zoomlevel ) ;
current _zoom = zoomlevel ;
$ . each ( selectedElements , function ( i , elem ) {
if ( ! elem ) return ;
selectorManager . requestSelector ( elem ) . resize ( ) ;
} ) ;
pathActions . zoomChange ( ) ;
runExtensions ( "zoomChanged" , zoomlevel ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getMode
// Returns the current editor mode string
this . getMode = function ( ) {
return current _mode ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setMode
// Sets the editor's mode to the given string
//
// Parameters:
// name - String with the new mode to change to
this . setMode = function ( name ) {
pathActions . clear ( true ) ;
textActions . clear ( ) ;
cur _properties = ( selectedElements [ 0 ] && selectedElements [ 0 ] . nodeName == 'text' ) ? cur _text : cur _shape ;
current _mode = name ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Group: Element Styling
// Function: getColor
// Returns the current fill/stroke option
this . getColor = function ( type ) {
return cur _properties [ type ] ;
} ;
// Function: setColor
// Change the current stroke/fill color/gradient value
//
// Parameters:
// type - String indicating fill or stroke
// val - The value to set the stroke attribute to
// preventUndo - Boolean indicating whether or not this should be and undoable option
this . setColor = function ( type , val , preventUndo ) {
cur _shape [ type ] = val ;
cur _properties [ type + '_paint' ] = { type : "solidColor" } ;
var elems = [ ] ;
var i = selectedElements . length ;
while ( i -- ) {
var elem = selectedElements [ i ] ;
if ( elem ) {
if ( elem . tagName == "g" )
walkTree ( elem , function ( e ) { if ( e . nodeName != "g" ) elems . push ( e ) ; } ) ;
else {
if ( type == 'fill' ) {
if ( elem . tagName != "polyline" && elem . tagName != "line" ) {
elems . push ( elem ) ;
}
} else {
2010-02-05 21:36:35 -06:00
elems . push ( elem ) ;
2010-07-20 07:59:47 -05:00
}
}
}
}
if ( elems . length > 0 ) {
if ( ! preventUndo ) {
changeSelectedAttribute ( type , val , elems ) ;
call ( "changed" , elems ) ;
} else
changeSelectedAttributeNoUndo ( type , val , elems ) ;
}
}
// Function: findDefs
// Return the document's <defs> element, create it first if necessary
var findDefs = function ( ) {
var defs = svgcontent . getElementsByTagNameNS ( svgns , "defs" ) ;
if ( defs . length > 0 ) {
defs = defs [ 0 ] ;
}
else {
// first child is a comment, so call nextSibling
defs = svgcontent . insertBefore ( svgdoc . createElementNS ( svgns , "defs" ) , svgcontent . firstChild . nextSibling ) ;
}
return defs ;
} ;
// Function: setGradient
// Apply the current gradient to selected element's fill or stroke
//
// Parameters
// type - String indicating "fill" or "stroke" to apply to an element
var setGradient = this . setGradient = function ( type ) {
if ( ! cur _properties [ type + '_paint' ] || cur _properties [ type + '_paint' ] . type == "solidColor" ) return ;
var grad = canvas [ type + 'Grad' ] ;
// find out if there is a duplicate gradient already in the defs
var duplicate _grad = findDuplicateGradient ( grad ) ;
var defs = findDefs ( ) ;
// no duplicate found, so import gradient into defs
if ( ! duplicate _grad ) {
var orig _grad = grad ;
grad = defs . appendChild ( svgdoc . importNode ( grad , true ) ) ;
// get next id and set it on the grad
grad . id = getNextId ( ) ;
}
else { // use existing gradient
grad = duplicate _grad ;
}
canvas . setColor ( type , "url(#" + grad . id + ")" ) ;
}
// Function: findDuplicateGradient
// Check if exact gradient already exists
//
// Parameters:
// grad - The gradient DOM element to compare to others
//
// Returns:
// The existing gradient if found, null if not
var findDuplicateGradient = function ( grad ) {
var defs = findDefs ( ) ;
var existing _grads = $ ( defs ) . find ( "linearGradient, radialGradient" ) ;
var i = existing _grads . length ;
var rad _attrs = [ 'r' , 'cx' , 'cy' , 'fx' , 'fy' ] ;
while ( i -- ) {
var og = existing _grads [ i ] ;
if ( grad . tagName == "linearGradient" ) {
if ( grad . getAttribute ( 'x1' ) != og . getAttribute ( 'x1' ) ||
grad . getAttribute ( 'y1' ) != og . getAttribute ( 'y1' ) ||
grad . getAttribute ( 'x2' ) != og . getAttribute ( 'x2' ) ||
grad . getAttribute ( 'y2' ) != og . getAttribute ( 'y2' ) )
{
continue ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} else {
var grad _attrs = $ ( grad ) . attr ( rad _attrs ) ;
var og _attrs = $ ( og ) . attr ( rad _attrs ) ;
var diff = false ;
$ . each ( rad _attrs , function ( i , attr ) {
if ( grad _attrs [ attr ] != og _attrs [ attr ] ) diff = true ;
} ) ;
if ( diff ) continue ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// else could be a duplicate, iterate through stops
var stops = grad . getElementsByTagNameNS ( svgns , "stop" ) ;
var ostops = og . getElementsByTagNameNS ( svgns , "stop" ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( stops . length != ostops . length ) {
continue ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var j = stops . length ;
while ( j -- ) {
var stop = stops [ j ] ;
var ostop = ostops [ j ] ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( stop . getAttribute ( 'offset' ) != ostop . getAttribute ( 'offset' ) ||
stop . getAttribute ( 'stop-opacity' ) != ostop . getAttribute ( 'stop-opacity' ) ||
stop . getAttribute ( 'stop-color' ) != ostop . getAttribute ( 'stop-color' ) )
{
break ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
if ( j == - 1 ) {
return og ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} // for each gradient in defs
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
return null ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setPaint
// Set a color/gradient to a fill/stroke
//
// Parameters:
// type - String with "fill" or "stroke"
// paint - The jGraduate paint object to apply
this . setPaint = function ( type , paint ) {
// make a copy
var p = new $ . jGraduate . Paint ( paint ) ;
this . setPaintOpacity ( type , p . alpha / 100 , true ) ;
// now set the current paint object
cur _properties [ type + '_paint' ] = p ;
switch ( p . type ) {
case "solidColor" :
this . setColor ( type , p . solidColor != "none" ? "#" + p . solidColor : "none" ) ; ;
break ;
case "linearGradient" :
case "radialGradient" :
canvas [ type + 'Grad' ] = p [ p . type ] ;
setGradient ( type ) ;
break ;
default :
// console.log("none!");
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// this.setStrokePaint = function(p) {
// // make a copy
// var p = new $.jGraduate.Paint(p);
// this.setStrokeOpacity(p.alpha/100);
//
// // now set the current paint object
// cur_properties.stroke_paint = p;
// switch ( p.type ) {
// case "solidColor":
// this.setColor('stroke', p.solidColor != "none" ? "#"+p.solidColor : "none");;
// break;
// case "linearGradient"
// case "radialGradient"
// canvas.strokeGrad = p[p.type];
// setGradient(type);
// default:
// // console.log("none!");
// }
// };
//
// this.setFillPaint = function(p, addGrad) {
// // make a copy
// var p = new $.jGraduate.Paint(p);
// this.setFillOpacity(p.alpha/100, true);
//
// // now set the current paint object
// cur_properties.fill_paint = p;
// if (p.type == "solidColor") {
// this.setColor('fill', p.solidColor != "none" ? "#"+p.solidColor : "none");
// }
// else if(p.type == "linearGradient") {
// canvas.fillGrad = p.linearGradient;
// if(addGrad) setGradient();
// }
// else if(p.type == "radialGradient") {
// canvas.fillGrad = p.radialGradient;
// if(addGrad) setGradient();
// }
// else {
// // console.log("none!");
// }
// };
// Function: getStrokeWidth
// Returns the current stroke-width value
this . getStrokeWidth = function ( ) {
return cur _properties . stroke _width ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setStrokeWidth
// Sets the stroke width for the current selected elements
// When attempting to set a line's width to 0, this changes it to 1 instead
//
// Parameters:
// val - A Float indicating the new stroke width value
this . setStrokeWidth = function ( val ) {
if ( val == 0 && $ . inArray ( current _mode , [ 'line' , 'path' ] ) != - 1 ) {
canvas . setStrokeWidth ( 1 ) ;
return ;
}
cur _properties . stroke _width = val ;
var elems = [ ] ;
var i = selectedElements . length ;
while ( i -- ) {
var elem = selectedElements [ i ] ;
if ( elem ) {
if ( elem . tagName == "g" )
walkTree ( elem , function ( e ) { if ( e . nodeName != "g" ) elems . push ( e ) ; } ) ;
else
elems . push ( elem ) ;
}
}
if ( elems . length > 0 ) {
changeSelectedAttribute ( "stroke-width" , val , elems ) ;
call ( "changed" , selectedElements ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setStrokeAttr
// Set the given stroke-related attribute the given value for selected elements
//
// Parameters:
// attr - String with the attribute name
// val - String or number with the attribute value
this . setStrokeAttr = function ( attr , val ) {
cur _shape [ attr . replace ( '-' , '_' ) ] = val ;
var elems = [ ] ;
var i = selectedElements . length ;
while ( i -- ) {
var elem = selectedElements [ i ] ;
if ( elem ) {
if ( elem . tagName == "g" )
walkTree ( elem , function ( e ) { if ( e . nodeName != "g" ) elems . push ( e ) ; } ) ;
else
elems . push ( elem ) ;
}
}
if ( elems . length > 0 ) {
changeSelectedAttribute ( attr , val , elems ) ;
call ( "changed" , selectedElements ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getStyle
// Returns current style options
this . getStyle = function ( ) {
return cur _shape ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getOpacity
// Returns the current opacity
this . getOpacity = function ( ) {
return cur _shape . opacity ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setOpacity
// Sets the given opacity to the current selected elements
this . setOpacity = function ( val ) {
cur _shape . opacity = val ;
changeSelectedAttribute ( "opacity" , val ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getOpacity
// Returns the current fill opacity
this . getFillOpacity = function ( ) {
return cur _shape . fill _opacity ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getStrokeOpacity
// Returns the current stroke opacity
this . getStrokeOpacity = function ( ) {
return cur _shape . stroke _opacity ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setPaintOpacity
// Sets the current fill/stroke opacity
//
// Parameters:
// type - String with "fill" or "stroke"
// val - Float with the new opacity value
// preventUndo - Boolean indicating whether or not this should be an undoable action
this . setPaintOpacity = function ( type , val , preventUndo ) {
cur _shape [ type + '_opacity' ] = val ;
if ( ! preventUndo )
changeSelectedAttribute ( type + "-opacity" , val ) ;
else
changeSelectedAttributeNoUndo ( type + "-opacity" , val ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getBlur
// Gets the stdDeviation blur value of the given element
//
// Parameters:
// elem - The element to check the blur value for
this . getBlur = function ( elem ) {
var val = 0 ;
// var elem = selectedElements[0];
if ( elem ) {
var filter _url = elem . getAttribute ( 'filter' ) ;
if ( filter _url ) {
var blur = getElem ( elem . id + '_blur' ) ;
if ( blur ) {
val = blur . firstChild . getAttribute ( 'stdDeviation' ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
}
return val ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
( function ( ) {
var cur _command = null ;
var filter = null ;
var filterHidden = false ;
// Function: setBlurNoUndo
// Sets the stdDeviation blur value on the selected element without being undoable
//
// Parameters:
// val - The new stdDeviation value
canvas . setBlurNoUndo = function ( val ) {
if ( ! filter ) {
canvas . setBlur ( val ) ;
return ;
}
if ( val === 0 ) {
// Don't change the StdDev, as that will hide the element.
// Instead, just remove the value for "filter"
changeSelectedAttributeNoUndo ( "filter" , "" ) ;
filterHidden = true ;
} else {
if ( filterHidden ) {
changeSelectedAttributeNoUndo ( "filter" , 'url(#' + selectedElements [ 0 ] . id + '_blur)' ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
changeSelectedAttributeNoUndo ( "stdDeviation" , val , [ filter . firstChild ] ) ;
canvas . setBlurOffsets ( filter , val ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
2010-04-01 23:56:21 -05:00
2010-07-20 07:59:47 -05:00
function finishChange ( ) {
var bCmd = canvas . finishUndoableChange ( ) ;
cur _command . addSubCommand ( bCmd ) ;
addCommandToHistory ( cur _command ) ;
cur _command = null ;
filter = null ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setBlurOffsets
// Sets the x, y, with, height values of the filter element in order to
// make the blur not be clipped. Removes them if not neeeded
//
// Parameters:
// filter - The filter DOM element to update
// stdDev - The standard deviation value on which to base the offset size
canvas . setBlurOffsets = function ( filter , stdDev ) {
if ( stdDev > 3 ) {
// TODO: Create algorithm here where size is based on expected blur
assignAttributes ( filter , {
x : '-50%' ,
y : '-50%' ,
width : '200%' ,
height : '200%' ,
} , 100 ) ;
} else {
filter . removeAttribute ( 'x' ) ;
filter . removeAttribute ( 'y' ) ;
filter . removeAttribute ( 'width' ) ;
filter . removeAttribute ( 'height' ) ;
2010-04-12 00:33:24 -05:00
}
2010-07-20 07:59:47 -05:00
}
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
// Function: setBlur
// Adds/updates the blur filter to the selected element
//
// Parameters:
// val - Float with the new stdDeviation blur value
// complete - Boolean indicating whether or not the action should be completed (to add to the undo manager)
canvas . setBlur = function ( val , complete ) {
if ( cur _command ) {
finishChange ( ) ;
return ;
2010-06-03 07:13:37 -05:00
}
2010-07-20 07:59:47 -05:00
// Looks for associated blur, creates one if not found
var elem = selectedElements [ 0 ] ;
var elem _id = elem . id ;
filter = getElem ( elem _id + '_blur' ) ;
2010-04-28 16:44:07 -05:00
2010-07-20 07:59:47 -05:00
val -= 0 ;
var batchCmd = new BatchCommand ( ) ;
// Blur found!
if ( filter ) {
2010-04-28 16:44:07 -05:00
if ( val === 0 ) {
2010-07-20 07:59:47 -05:00
filter = null ;
2010-02-05 21:36:35 -06:00
}
} else {
2010-07-20 07:59:47 -05:00
// Not found, so create
var newblur = addSvgElementFromJson ( { "element" : "feGaussianBlur" ,
"attr" : {
"in" : 'SourceGraphic' ,
"stdDeviation" : val
2010-02-18 10:12:44 -06:00
}
2010-07-20 07:59:47 -05:00
} ) ;
filter = addSvgElementFromJson ( { "element" : "filter" ,
"attr" : {
"id" : elem _id + '_blur'
}
} ) ;
filter . appendChild ( newblur ) ;
findDefs ( ) . appendChild ( filter ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( filter ) ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var changes = { filter : elem . getAttribute ( 'filter' ) } ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( val === 0 ) {
elem . removeAttribute ( "filter" ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , changes ) ) ;
return ;
} else {
changeSelectedAttribute ( "filter" , 'url(#' + elem _id + '_blur)' ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , changes ) ) ;
canvas . setBlurOffsets ( filter , val ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
cur _command = batchCmd ;
canvas . beginUndoableChange ( "stdDeviation" , [ filter ? filter . firstChild : null ] ) ;
if ( complete ) {
canvas . setBlurNoUndo ( val ) ;
finishChange ( ) ;
2010-02-05 21:36:35 -06:00
}
} ;
2010-07-20 07:59:47 -05:00
} ( ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getBold
// Check whether selected element is bold or not
//
// Returns:
// Boolean indicating whether or not element is bold
this . getBold = function ( ) {
// should only have one element selected
var selected = selectedElements [ 0 ] ;
if ( selected != null && selected . tagName == "text" &&
selectedElements [ 1 ] == null )
{
return ( selected . getAttribute ( "font-weight" ) == "bold" ) ;
}
return false ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setBold
// Make the selected element bold or normal
//
// Parameters:
// b - Boolean indicating bold (true) or normal (false)
this . setBold = function ( b ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null && selected . tagName == "text" &&
selectedElements [ 1 ] == null )
{
changeSelectedAttribute ( "font-weight" , b ? "bold" : "normal" ) ;
}
2010-08-13 16:14:19 -05:00
if ( ! selectedElements [ 0 ] . textContent ) {
textActions . setCursor ( ) ;
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getItalic
// Check whether selected element is italic or not
//
// Returns:
// Boolean indicating whether or not element is italic
this . getItalic = function ( ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null && selected . tagName == "text" &&
selectedElements [ 1 ] == null )
{
return ( selected . getAttribute ( "font-style" ) == "italic" ) ;
}
return false ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setItalic
// Make the selected element italic or normal
//
// Parameters:
// b - Boolean indicating italic (true) or normal (false)
this . setItalic = function ( i ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null && selected . tagName == "text" &&
selectedElements [ 1 ] == null )
{
changeSelectedAttribute ( "font-style" , i ? "italic" : "normal" ) ;
}
2010-08-13 16:14:19 -05:00
if ( ! selectedElements [ 0 ] . textContent ) {
textActions . setCursor ( ) ;
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getFontFamily
// Returns the current font family
this . getFontFamily = function ( ) {
return cur _text . font _family ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setFontFamily
// Set the new font family
//
// Parameters:
// val - String with the new font family
this . setFontFamily = function ( val ) {
cur _text . font _family = val ;
changeSelectedAttribute ( "font-family" , val ) ;
2010-08-13 16:14:19 -05:00
if ( selectedElements [ 0 ] && ! selectedElements [ 0 ] . textContent ) {
textActions . setCursor ( ) ;
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getFontSize
// Returns the current font size
this . getFontSize = function ( ) {
return cur _text . font _size ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setFontSize
// Applies the given font size to the selected element
//
// Parameters:
// val - Float with the new font size
this . setFontSize = function ( val ) {
cur _text . font _size = val ;
changeSelectedAttribute ( "font-size" , val ) ;
2010-08-13 16:14:19 -05:00
if ( ! selectedElements [ 0 ] . textContent ) {
textActions . setCursor ( ) ;
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: getText
// Returns the current text (textContent) of the selected element
this . getText = function ( ) {
var selected = selectedElements [ 0 ] ;
if ( selected == null ) { return "" ; }
return selected . textContent ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setTextContent
// Updates the text element with the given string
//
// Parameters:
// val - String with the new text
this . setTextContent = function ( val ) {
changeSelectedAttribute ( "#text" , val ) ;
textActions . init ( val ) ;
textActions . setCursor ( ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setImageURL
// Sets the new image URL for the selected image element. Updates its size if
// a new URL is given
//
// Parameters:
// val - String with the image URL/path
this . setImageURL = function ( val ) {
var elem = selectedElements [ 0 ] ;
if ( ! elem ) return ;
var attrs = $ ( elem ) . attr ( [ 'width' , 'height' ] ) ;
var setsize = ( ! attrs . width || ! attrs . height ) ;
2010-02-05 21:36:35 -06:00
2010-09-10 15:19:23 -05:00
var cur _href = getHref ( elem ) ;
2010-04-18 12:55:02 -05:00
2010-07-20 07:59:47 -05:00
// Do nothing if no URL change or size change
if ( cur _href !== val ) {
setsize = true ;
} else if ( ! setsize ) return ;
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
var batchCmd = new BatchCommand ( "Change Image URL" ) ;
2010-04-12 00:33:24 -05:00
2010-09-10 15:19:23 -05:00
setHref ( elem , val ) ;
2010-07-20 07:59:47 -05:00
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , {
"#href" : cur _href
} ) ) ;
if ( setsize ) {
$ ( new Image ( ) ) . load ( function ( ) {
var changes = $ ( elem ) . attr ( [ 'width' , 'height' ] ) ;
$ ( elem ) . attr ( {
width : this . width ,
height : this . height
} ) ;
2010-04-12 00:33:24 -05:00
2010-07-20 07:59:47 -05:00
selectorManager . requestSelector ( elem ) . resize ( ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( elem , changes ) ) ;
2010-04-12 00:33:24 -05:00
addCommandToHistory ( batchCmd ) ;
2010-07-20 07:59:47 -05:00
call ( "changed" , [ elem ] ) ;
} ) . attr ( 'src' , val ) ;
} else {
addCommandToHistory ( batchCmd ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: setRectRadius
// Sets the rx & ry values to the selected rect element to change its corner radius
//
// Parameters:
// val - The new radius
this . setRectRadius = function ( val ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null && selected . tagName == "rect" ) {
var r = selected . getAttribute ( "rx" ) ;
if ( r != val ) {
selected . setAttribute ( "rx" , val ) ;
selected . setAttribute ( "ry" , val ) ;
addCommandToHistory ( new ChangeElementCommand ( selected , { "rx" : r , "ry" : r } , "Radius" ) ) ;
call ( "changed" , [ selected ] ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
} ;
// Group: Element manipulation
// Function: setSegType
// Sets the new segment type to the selected segment(s).
//
// Parameters:
// new_type - Integer with the new segment type
// See http://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg for list
this . setSegType = function ( new _type ) {
pathActions . setSegType ( new _type ) ;
}
// Function: convertToPath
// Convert selected element to a path, or get the BBox of an element-as-path
//
// Parameters:
// elem - The DOM element to be converted
// getBBox - Boolean on whether or not to only return the path's BBox
//
// Returns:
// If the getBBox flag is true, the resulting path's bounding box object.
// Otherwise the resulting path element is returned.
this . convertToPath = function ( elem , getBBox ) {
if ( elem == null ) {
var elems = selectedElements ;
$ . each ( selectedElements , function ( i , elem ) {
if ( elem ) canvas . convertToPath ( elem ) ;
} ) ;
return ;
}
if ( ! getBBox ) {
var batchCmd = new BatchCommand ( "Convert element to Path" ) ;
}
var attrs = getBBox ? { } : {
"fill" : cur _shape . fill ,
"fill-opacity" : cur _shape . fill _opacity ,
"stroke" : cur _shape . stroke ,
"stroke-width" : cur _shape . stroke _width ,
"stroke-dasharray" : cur _shape . stroke _dasharray ,
"stroke-linejoin" : cur _shape . stroke _linejoin ,
"stroke-linecap" : cur _shape . stroke _linecap ,
"stroke-opacity" : cur _shape . stroke _opacity ,
"opacity" : cur _shape . opacity ,
"visibility" : "hidden"
2010-02-05 21:36:35 -06:00
} ;
2010-07-20 07:59:47 -05:00
// any attribute on the element not covered by the above
// TODO: make this list global so that we can properly maintain it
// TODO: what about @transform, @clip-rule, @fill-rule, etc?
$ . each ( [ 'marker-start' , 'marker-end' , 'marker-mid' , 'filter' , 'clip-path' ] , function ( ) {
if ( elem . getAttribute ( this ) ) {
attrs [ this ] = elem . getAttribute ( this ) ;
}
} ) ;
var path = addSvgElementFromJson ( {
"element" : "path" ,
"attr" : attrs
} ) ;
var eltrans = elem . getAttribute ( "transform" ) ;
if ( eltrans ) {
path . setAttribute ( "transform" , eltrans ) ;
}
var id = elem . id ;
var parent = elem . parentNode ;
if ( elem . nextSibling ) {
parent . insertBefore ( path , elem ) ;
} else {
parent . appendChild ( path ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
var d = '' ;
var joinSegs = function ( segs ) {
$ . each ( segs , function ( j , seg ) {
var l = seg [ 0 ] , pts = seg [ 1 ] ;
d += l ;
for ( var i = 0 ; i < pts . length ; i += 2 ) {
d += ( pts [ i ] + ',' + pts [ i + 1 ] ) + ' ' ;
}
} ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81 ;
switch ( elem . tagName ) {
case 'ellipse' :
case 'circle' :
var a = $ ( elem ) . attr ( [ 'rx' , 'ry' , 'cx' , 'cy' ] ) ;
var cx = a . cx , cy = a . cy , rx = a . rx , ry = a . ry ;
if ( elem . tagName == 'circle' ) {
rx = ry = $ ( elem ) . attr ( 'r' ) ;
}
joinSegs ( [
[ 'M' , [ ( cx - rx ) , ( cy ) ] ] ,
[ 'C' , [ ( cx - rx ) , ( cy - ry / num ) , ( cx - rx / num ) , ( cy - ry ) , ( cx ) , ( cy - ry ) ] ] ,
[ 'C' , [ ( cx + rx / num ) , ( cy - ry ) , ( cx + rx ) , ( cy - ry / num ) , ( cx + rx ) , ( cy ) ] ] ,
[ 'C' , [ ( cx + rx ) , ( cy + ry / num ) , ( cx + rx / num ) , ( cy + ry ) , ( cx ) , ( cy + ry ) ] ] ,
[ 'C' , [ ( cx - rx / num ) , ( cy + ry ) , ( cx - rx ) , ( cy + ry / num ) , ( cx - rx ) , ( cy ) ] ] ,
[ 'Z' , [ ] ]
] ) ;
break ;
case 'path' :
d = elem . getAttribute ( 'd' ) ;
break ;
case 'line' :
var a = $ ( elem ) . attr ( [ "x1" , "y1" , "x2" , "y2" ] ) ;
d = "M" + a . x1 + "," + a . y1 + "L" + a . x2 + "," + a . y2 ;
break ;
case 'polyline' :
case 'polygon' :
d = "M" + elem . getAttribute ( 'points' ) ;
break ;
case 'rect' :
var r = $ ( elem ) . attr ( [ 'rx' , 'ry' ] ) ;
var rx = r . rx , ry = r . ry ;
var b = elem . getBBox ( ) ;
var x = b . x , y = b . y , w = b . width , h = b . height ;
var num = 4 - num ; // Why? Because!
if ( ! rx && ! ry ) {
// Regular rect
joinSegs ( [
[ 'M' , [ x , y ] ] ,
[ 'L' , [ x + w , y ] ] ,
[ 'L' , [ x + w , y + h ] ] ,
[ 'L' , [ x , y + h ] ] ,
[ 'L' , [ x , y ] ] ,
[ 'Z' , [ ] ]
] ) ;
} else {
joinSegs ( [
[ 'M' , [ x , y + ry ] ] ,
[ 'C' , [ x , y + ry / num , x + rx / num , y , x + rx , y ] ] ,
[ 'L' , [ x + w - rx , y ] ] ,
[ 'C' , [ x + w - rx / num , y , x + w , y + ry / num , x + w , y + ry ] ] ,
[ 'L' , [ x + w , y + h - ry ] ] ,
[ 'C' , [ x + w , y + h - ry / num , x + w - rx / num , y + h , x + w - rx , y + h ] ] ,
[ 'L' , [ x + rx , y + h ] ] ,
[ 'C' , [ x + rx / num , y + h , x , y + h - ry / num , x , y + h - ry ] ] ,
[ 'L' , [ x , y + ry ] ] ,
[ 'Z' , [ ] ]
] ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
break ;
default :
path . parentNode . removeChild ( path ) ;
break ;
}
if ( d ) {
path . setAttribute ( 'd' , d ) ;
}
if ( ! getBBox ) {
// Replace the current element with the converted one
// Reorient if it has a matrix
if ( eltrans ) {
var tlist = getTransformList ( path ) ;
if ( hasMatrixTransform ( tlist ) ) {
pathActions . resetOrientation ( path ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
batchCmd . addSubCommand ( new RemoveElementCommand ( elem , parent ) ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( path ) ) ;
clearSelection ( ) ;
elem . parentNode . removeChild ( elem )
path . setAttribute ( 'id' , id ) ;
path . removeAttribute ( "visibility" ) ;
addToSelection ( [ path ] , true ) ;
addCommandToHistory ( batchCmd ) ;
} else {
// Get the correct BBox of the new path, then discard it
pathActions . resetOrientation ( path ) ;
var bb = false ;
try {
bb = path . getBBox ( ) ;
} catch ( e ) {
// Firefox fails
}
path . parentNode . removeChild ( path ) ;
return bb ;
}
}
// Function: changeSelectedAttributeNoUndo
// This function makes the changes to the elements. It does not add the change
// to the history stack.
//
// Parameters:
// attr - String with the attribute name
// newValue - String or number with the new attribute value
// elems - The DOM elements to apply the change to
var changeSelectedAttributeNoUndo = function ( attr , newValue , elems ) {
var handle = svgroot . suspendRedraw ( 1000 ) ;
if ( current _mode == 'pathedit' ) {
// Editing node
pathActions . moveNode ( attr , newValue ) ;
}
var elems = elems || selectedElements ;
var i = elems . length ;
while ( i -- ) {
var elem = elems [ i ] ;
if ( elem == null ) continue ;
// Go into "select" mode for text changes
2010-08-13 16:14:19 -05:00
if ( current _mode === "textedit" && attr !== "#text" && elem . textContent . length ) {
2010-07-20 07:59:47 -05:00
textActions . toSelectMode ( elem ) ;
}
// Set x,y vals on elements that don't have them
if ( ( attr == 'x' || attr == 'y' ) && $ . inArray ( elem . tagName , [ 'g' , 'polyline' , 'path' ] ) != - 1 ) {
var bbox = getStrokedBBox ( [ elem ] ) ;
var diff _x = attr == 'x' ? newValue - bbox . x : 0 ;
var diff _y = attr == 'y' ? newValue - bbox . y : 0 ;
canvas . moveSelectedElements ( diff _x * current _zoom , diff _y * current _zoom , true ) ;
continue ;
}
// only allow the transform/opacity attribute to change on <g> elements, slightly hacky
if ( elem . tagName == "g" && $ . inArray ( attr , [ 'transform' , 'opacity' , 'filter' ] ) !== - 1 ) ;
var oldval = attr == "#text" ? elem . textContent : elem . getAttribute ( attr ) ;
if ( oldval == null ) oldval = "" ;
if ( oldval != String ( newValue ) ) {
if ( attr == "#text" ) {
var old _w = getBBox ( elem ) . width ;
elem . textContent = newValue ;
elem = ffClone ( elem ) ;
// Hoped to solve the issue of moving text with text-anchor="start",
// but this doesn't actually fix it. Hopefully on the right track, though. -Fyrd
// var box=getBBox(elem), left=box.x, top=box.y, width=box.width,
2010-02-05 21:36:35 -06:00
// height=box.height, dx = width - old_w, dy=0;
2010-07-20 07:59:47 -05:00
// var angle = getRotationAngle(elem, true);
2010-02-05 21:36:35 -06:00
// if (angle) {
// var r = Math.sqrt( dx*dx + dy*dy );
// var theta = Math.atan2(dy,dx) - angle;
// dx = r * Math.cos(theta);
// dy = r * Math.sin(theta);
//
// elem.setAttribute('x', elem.getAttribute('x')-dx);
// elem.setAttribute('y', elem.getAttribute('y')-dy);
// }
2010-06-03 07:13:37 -05:00
2010-07-20 07:59:47 -05:00
} else if ( attr == "#href" ) {
2010-09-10 15:19:23 -05:00
setHref ( elem , newValue ) ;
2010-07-20 07:59:47 -05:00
}
else elem . setAttribute ( attr , newValue ) ;
if ( i == 0 )
selectedBBoxes [ i ] = getBBox ( elem ) ;
// Use the Firefox ffClone hack for text elements with gradients or
// where other text attributes are changed.
if ( elem . nodeName == 'text' ) {
2010-08-13 16:14:19 -05:00
if ( ( newValue + '' ) . indexOf ( 'url' ) == 0 || $ . inArray ( attr , [ 'font-size' , 'font-family' , 'x' , 'y' ] ) != - 1 && elem . textContent ) {
2010-07-20 07:59:47 -05:00
elem = ffClone ( elem ) ;
2010-06-03 07:13:37 -05:00
}
2010-07-20 07:59:47 -05:00
}
// Timeout needed for Opera & Firefox
// codedread: it is now possible for this function to be called with elements
// that are not in the selectedElements array, we need to only request a
// selector if the element is in that array
if ( $ . inArray ( elem , selectedElements ) != - 1 ) {
setTimeout ( function ( ) {
// Due to element replacement, this element may no longer
// be part of the DOM
if ( ! elem . parentNode ) return ;
selectorManager . requestSelector ( elem ) . resize ( ) ;
} , 0 ) ;
}
// if this element was rotated, and we changed the position of this element
// we need to update the rotational transform attribute
var angle = getRotationAngle ( elem ) ;
if ( angle != 0 && attr != "transform" ) {
var tlist = getTransformList ( elem ) ;
var n = tlist . numberOfItems ;
while ( n -- ) {
var xform = tlist . getItem ( n ) ;
if ( xform . type == 4 ) {
// remove old rotate
tlist . removeItem ( n ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var box = getBBox ( elem ) ;
var center = transformPoint ( box . x + box . width / 2 , box . y + box . height / 2 , transformListToTransform ( tlist ) . matrix ) ;
var cx = center . x ,
cy = center . y ;
var newrot = svgroot . createSVGTransform ( ) ;
newrot . setRotate ( angle , cx , cy ) ;
tlist . insertItemBefore ( newrot , n ) ;
break ;
2010-02-05 21:36:35 -06:00
}
}
}
2010-07-20 07:59:47 -05:00
} // if oldValue != newValue
} // for each elem
svgroot . unsuspendRedraw ( handle ) ;
} ;
2010-06-03 07:13:37 -05:00
2010-07-20 07:59:47 -05:00
// Function: changeSelectedAttribute
// Change the given/selected element and add the original value to the history stack
// If you want to change all selectedElements, ignore the elems argument.
// If you want to change only a subset of selectedElements, then send the
// subset to this function in the elems argument.
//
// Parameters:
// attr - String with the attribute name
// newValue - String or number with the new attribute value
// elems - The DOM elements to apply the change to
var changeSelectedAttribute = this . changeSelectedAttribute = function ( attr , val , elems ) {
var elems = elems || selectedElements ;
canvas . beginUndoableChange ( attr , elems ) ;
var i = elems . length ;
changeSelectedAttributeNoUndo ( attr , val , elems ) ;
var batchCmd = canvas . finishUndoableChange ( ) ;
if ( ! batchCmd . isEmpty ( ) ) {
addCommandToHistory ( batchCmd ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: deleteSelectedElements
// Removes all selected elements from the DOM and adds the change to the
// history stack
this . deleteSelectedElements = function ( ) {
var batchCmd = new BatchCommand ( "Delete Elements" ) ;
var len = selectedElements . length ;
var selectedCopy = [ ] ; //selectedElements is being deleted
for ( var i = 0 ; i < len ; ++ i ) {
var selected = selectedElements [ i ] ;
if ( selected == null ) break ;
var parent = selected . parentNode ;
var t = selected ;
// this will unselect the element and remove the selectedOutline
selectorManager . releaseSelector ( t ) ;
var elem = parent . removeChild ( t ) ;
selectedCopy . push ( selected ) //for the copy
selectedElements [ i ] = null ;
batchCmd . addSubCommand ( new RemoveElementCommand ( elem , parent ) ) ;
}
if ( ! batchCmd . isEmpty ( ) ) addCommandToHistory ( batchCmd ) ;
call ( "changed" , selectedCopy ) ;
clearSelection ( ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-09-10 15:19:23 -05:00
// Function: cutSelectedElements
// Removes all selected elements from the DOM and adds the change to the
// history stack. Remembers removed elements on the clipboard
// TODO: Combine similar code with deleteSelectedElements
this . cutSelectedElements = function ( ) {
var batchCmd = new BatchCommand ( "Cut Elements" ) ;
var len = selectedElements . length ;
var selectedCopy = [ ] ; //selectedElements is being deleted
for ( var i = 0 ; i < len ; ++ i ) {
var selected = selectedElements [ i ] ;
if ( selected == null ) break ;
var parent = selected . parentNode ;
var t = selected ;
// this will unselect the element and remove the selectedOutline
selectorManager . releaseSelector ( t ) ;
var elem = parent . removeChild ( t ) ;
selectedCopy . push ( selected ) //for the copy
selectedElements [ i ] = null ;
batchCmd . addSubCommand ( new RemoveElementCommand ( elem , parent ) ) ;
}
if ( ! batchCmd . isEmpty ( ) ) addCommandToHistory ( batchCmd ) ;
call ( "changed" , selectedCopy ) ;
clearSelection ( ) ;
canvas . clipBoard = selectedCopy ;
} ;
// Function: copySelectedElements
// Remembers the current selected elements on the clipboard
this . copySelectedElements = function ( ) {
canvas . clipBoard = $ . merge ( [ ] , selectedElements ) ;
} ;
this . pasteElements = function ( type ) {
var cb = canvas . clipBoard ;
var len = cb . length ;
if ( ! len ) return ;
var pasted = [ ] ;
var batchCmd = new BatchCommand ( 'Paste elements' ) ;
// Move elements to lastClickPoint
while ( len -- ) {
var elem = cb [ len ] ;
if ( ! elem ) continue ;
var copy = copyElem ( elem ) ;
// See if elem with elem ID is in the DOM already
if ( ! getElem ( elem . id ) ) copy . id = elem . id ;
pasted . push ( copy ) ;
current _layer . appendChild ( copy ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( copy ) ) ;
}
clearSelection ( true ) ;
addToSelection ( pasted ) ;
if ( type !== 'in_place' ) {
var bbox = getStrokedBBox ( pasted ) ;
var cx = lastClickPoint . x - ( bbox . x + bbox . width / 2 ) ,
cy = lastClickPoint . y - ( bbox . y + bbox . height / 2 ) ,
dx = [ ] ,
dy = [ ] ;
$ . each ( pasted , function ( i , item ) {
dx . push ( cx ) ;
dy . push ( cy ) ;
} ) ;
var cmd = canvas . moveSelectedElements ( dx , dy , false ) ;
batchCmd . addSubCommand ( cmd ) ;
}
addCommandToHistory ( batchCmd ) ;
call ( "changed" , pasted ) ;
}
2010-07-20 07:59:47 -05:00
// Function: groupSelectedElements
// Wraps all the selected elements in a group (g) element
this . groupSelectedElements = function ( ) {
var batchCmd = new BatchCommand ( "Group Elements" ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// create and insert the group element
var g = addSvgElementFromJson ( {
"element" : "g" ,
"attr" : {
"id" : getNextId ( )
}
} ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( g ) ) ;
// now move all children into the group
var i = selectedElements . length ;
while ( i -- ) {
var elem = selectedElements [ i ] ;
if ( elem == null ) continue ;
var oldNextSibling = elem . nextSibling ;
var oldParent = elem . parentNode ;
g . appendChild ( elem ) ;
batchCmd . addSubCommand ( new MoveElementCommand ( elem , oldNextSibling , oldParent ) ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
if ( ! batchCmd . isEmpty ( ) ) addCommandToHistory ( batchCmd ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// update selection
clearSelection ( ) ;
addToSelection ( [ g ] , true ) ;
} ;
// Function: ungroupSelectedElement
// Unwraps all the elements in a selected group (g) element. This requires
// significant recalculations to apply group's transforms, etc to its children
this . ungroupSelectedElement = function ( ) {
var g = selectedElements [ 0 ] ;
2010-08-13 16:14:19 -05:00
if ( $ ( g ) . data ( 'gsvg' ) || $ ( g ) . data ( 'symbol' ) ) {
// Is svg, so actually convert to group
convertToGroup ( g ) ;
return ;
}
2010-07-20 07:59:47 -05:00
if ( g . tagName == "g" ) {
2010-08-13 16:14:19 -05:00
2010-07-20 07:59:47 -05:00
var batchCmd = new BatchCommand ( "Ungroup Elements" ) ;
var parent = g . parentNode ;
2010-08-13 16:14:19 -05:00
var anchor = g . nextSibling ;
2010-07-20 07:59:47 -05:00
var children = new Array ( g . childNodes . length ) ;
var xform = g . getAttribute ( "transform" ) ;
// get consolidated matrix
var glist = getTransformList ( g ) ;
var m = transformListToTransform ( glist ) . matrix ;
// TODO: get all fill/stroke properties from the group that we are about to destroy
// "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset",
// "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity",
// "stroke-width"
// and then for each child, if they do not have the attribute (or the value is 'inherit')
// then set the child's attribute
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var i = 0 ;
var gangle = getRotationAngle ( g ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var gattrs = $ ( g ) . attr ( [ 'filter' , 'opacity' ] ) ;
var gfilter , gblur ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
while ( g . firstChild ) {
var elem = g . firstChild ;
var oldNextSibling = elem . nextSibling ;
var oldParent = elem . parentNode ;
2010-08-13 16:14:19 -05:00
// Remove child title elements
if ( elem . tagName == 'title' ) {
batchCmd . addSubCommand ( new RemoveElementCommand ( elem , oldParent ) ) ;
oldParent . removeChild ( elem ) ;
continue ;
}
2010-07-20 07:59:47 -05:00
children [ i ++ ] = elem = parent . insertBefore ( elem , anchor ) ;
batchCmd . addSubCommand ( new MoveElementCommand ( elem , oldNextSibling , oldParent ) ) ;
if ( gattrs . opacity !== null && gattrs . opacity !== 1 ) {
var c _opac = elem . getAttribute ( 'opacity' ) || 1 ;
var new _opac = Math . round ( ( elem . getAttribute ( 'opacity' ) || 1 ) * gattrs . opacity * 100 ) / 100 ;
changeSelectedAttribute ( 'opacity' , new _opac , [ elem ] ) ;
}
if ( gattrs . filter ) {
var cblur = this . getBlur ( elem ) ;
var orig _cblur = cblur ;
if ( ! gblur ) gblur = this . getBlur ( g ) ;
if ( cblur ) {
// Is this formula correct?
cblur = ( gblur - 0 ) + ( cblur - 0 ) ;
} else if ( cblur === 0 ) {
cblur = gblur ;
}
// If child has no current filter, get group's filter or clone it.
if ( ! orig _cblur ) {
// Set group's filter to use first child's ID
if ( ! gfilter ) {
gfilter = getElem ( getUrlFromAttr ( gattrs . filter ) . substr ( 1 ) ) ;
} else {
// Clone the group's filter
gfilter = copyElem ( gfilter ) ;
findDefs ( ) . appendChild ( gfilter ) ;
}
} else {
gfilter = getElem ( getUrlFromAttr ( elem . getAttribute ( 'filter' ) ) . substr ( 1 ) ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Change this in future for different filters
var suffix = ( gfilter . firstChild . tagName === 'feGaussianBlur' ) ? 'blur' : 'filter' ;
gfilter . id = elem . id + '_' + suffix ;
changeSelectedAttribute ( 'filter' , 'url(#' + gfilter . id + ')' , [ elem ] ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Update blur value
if ( cblur ) {
changeSelectedAttribute ( 'stdDeviation' , cblur , [ gfilter . firstChild ] ) ;
canvas . setBlurOffsets ( gfilter , cblur ) ;
}
}
var chtlist = getTransformList ( elem ) ;
2010-08-13 16:14:19 -05:00
// Hopefully not a problem to add this. Necessary for elements like <desc/>
if ( ! chtlist ) continue ;
2010-07-20 07:59:47 -05:00
if ( glist . numberOfItems ) {
// TODO: if the group's transform is just a rotate, we can always transfer the
// rotate() down to the children (collapsing consecutive rotates and factoring
// out any translates)
if ( gangle && glist . numberOfItems == 1 ) {
// [Rg] [Rc] [Mc]
// we want [Tr] [Rc2] [Mc] where:
// - [Rc2] is at the child's current center but has the
// sum of the group and child's rotation angles
// - [Tr] is the equivalent translation that this child
// undergoes if the group wasn't there
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// [Tr] = [Rg] [Rc] [Rc2_inv]
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// get group's rotation matrix (Rg)
var rgm = glist . getItem ( 0 ) . matrix ;
// get child's rotation matrix (Rc)
var rcm = svgroot . createSVGMatrix ( ) ;
var cangle = getRotationAngle ( elem ) ;
if ( cangle ) {
rcm = chtlist . getItem ( 0 ) . matrix ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// get child's old center of rotation
var cbox = getBBox ( elem ) ;
var ceqm = transformListToTransform ( chtlist ) . matrix ;
var coldc = transformPoint ( cbox . x + cbox . width / 2 , cbox . y + cbox . height / 2 , ceqm ) ;
// sum group and child's angles
var sangle = gangle + cangle ;
// get child's rotation at the old center (Rc2_inv)
var r2 = svgroot . createSVGTransform ( ) ;
r2 . setRotate ( sangle , coldc . x , coldc . y ) ;
// calculate equivalent translate
var trm = matrixMultiply ( rgm , rcm , r2 . matrix . inverse ( ) ) ;
// set up tlist
if ( cangle ) {
chtlist . removeItem ( 0 ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
if ( sangle ) {
chtlist . insertItemBefore ( r2 , 0 ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( trm . e || trm . f ) {
var tr = svgroot . createSVGTransform ( ) ;
tr . setTranslate ( trm . e , trm . f ) ;
chtlist . insertItemBefore ( tr , 0 ) ;
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
else { // more complicated than just a rotate
// transfer the group's transform down to each child and then
// call recalculateDimensions()
var oldxform = elem . getAttribute ( "transform" ) ;
var changes = { } ;
changes [ "transform" ] = oldxform ? oldxform : "" ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var newxform = svgroot . createSVGTransform ( ) ;
// [ gm ] [ chm ] = [ chm ] [ gm' ]
// [ gm' ] = [ chm_inv ] [ gm ] [ chm ]
var chm = transformListToTransform ( chtlist ) . matrix ,
chm _inv = chm . inverse ( ) ;
var gm = matrixMultiply ( chm _inv , m , chm ) ;
newxform . setMatrix ( gm ) ;
chtlist . appendItem ( newxform ) ;
}
batchCmd . addSubCommand ( recalculateDimensions ( elem ) ) ;
2010-02-22 18:53:15 -06:00
}
2010-07-20 07:59:47 -05:00
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// remove transform and make it undo-able
if ( xform ) {
var changes = { } ;
changes [ "transform" ] = xform ;
g . setAttribute ( "transform" , "" ) ;
g . removeAttribute ( "transform" ) ;
batchCmd . addSubCommand ( new ChangeElementCommand ( g , changes ) ) ;
}
// remove the group from the selection
clearSelection ( ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// delete the group element (but make undo-able)
g = parent . removeChild ( g ) ;
batchCmd . addSubCommand ( new RemoveElementCommand ( g , parent ) ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( ! batchCmd . isEmpty ( ) ) addCommandToHistory ( batchCmd ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// update selection
addToSelection ( children ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: moveToTopSelectedElement
// Repositions the selected element to the bottom in the DOM to appear on top of
// other elements
this . moveToTopSelectedElement = function ( ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null ) {
var t = selected ;
var oldParent = t . parentNode ;
var oldNextSibling = t . nextSibling ;
t = t . parentNode . appendChild ( t ) ;
addCommandToHistory ( new MoveElementCommand ( t , oldNextSibling , oldParent , "top" ) ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: moveToBottomSelectedElement
// Repositions the selected element to the top in the DOM to appear under
// other elements
this . moveToBottomSelectedElement = function ( ) {
var selected = selectedElements [ 0 ] ;
if ( selected != null ) {
var t = selected ;
var oldParent = t . parentNode ;
var oldNextSibling = t . nextSibling ;
var firstChild = t . parentNode . firstChild ;
if ( firstChild . tagName == 'title' ) {
firstChild = firstChild . nextSibling ;
}
// This can probably be removed, as the defs should not ever apppear
// inside a layer group
if ( firstChild . tagName == 'defs' ) {
firstChild = firstChild . nextSibling ;
}
t = t . parentNode . insertBefore ( t , firstChild ) ;
addCommandToHistory ( new MoveElementCommand ( t , oldNextSibling , oldParent , "bottom" ) ) ;
}
} ;
2010-02-05 21:36:35 -06:00
2010-09-10 15:19:23 -05:00
// Function: moveUpDownSelected
// Moves the select element up or down the stack, based on the visibly
// intersecting elements
//
// Parameters:
// dir - String that's either 'Up' or 'Down'
this . moveUpDownSelected = function ( dir ) {
var selected = selectedElements [ 0 ] ;
if ( ! selected ) return ;
curBBoxes = [ ] ;
var closest , found _cur ;
// jQuery sorts this list
var list = $ ( getIntersectionList ( getStrokedBBox ( [ selected ] ) ) ) . toArray ( ) ;
if ( dir == 'Down' ) list . reverse ( ) ;
$ . each ( list , function ( ) {
if ( ! found _cur ) {
if ( this == selected ) {
found _cur = true ;
}
return ;
}
closest = this ;
return false ;
} ) ;
if ( ! closest ) return ;
var t = selected ;
var oldParent = t . parentNode ;
var oldNextSibling = t . nextSibling ;
$ ( closest ) [ dir == 'Down' ? 'before' : 'after' ] ( t ) ;
addCommandToHistory ( new MoveElementCommand ( t , oldNextSibling , oldParent , "Move " + dir ) ) ;
}
2010-07-20 07:59:47 -05:00
// Function: moveSelectedElements
// Moves selected elements on the X/Y axis
//
// Parameters:
// dx - Float with the distance to move on the x-axis
// dy - Float with the distance to move on the y-axis
// undoable - Boolean indicating whether or not the action should be undoable
//
// Returns:
// Batch command for the move
this . moveSelectedElements = function ( dx , dy , undoable ) {
// if undoable is not sent, default to true
// if single values, scale them to the zoom
if ( dx . constructor != Array ) {
dx /= current _zoom ;
dy /= current _zoom ;
}
var undoable = undoable || true ;
var batchCmd = new BatchCommand ( "position" ) ;
var i = selectedElements . length ;
while ( i -- ) {
var selected = selectedElements [ i ] ;
if ( selected != null ) {
if ( i == 0 )
selectedBBoxes [ i ] = getBBox ( selected ) ;
2010-09-10 15:19:23 -05:00
var b = { } ;
for ( var j in selectedBBoxes [ i ] ) b [ j ] = selectedBBoxes [ i ] [ j ] ;
selectedBBoxes [ i ] = b ;
2010-07-20 07:59:47 -05:00
var xform = svgroot . createSVGTransform ( ) ;
var tlist = getTransformList ( selected ) ;
// dx and dy could be arrays
if ( dx . constructor == Array ) {
if ( i == 0 ) {
selectedBBoxes [ i ] . x += dx [ i ] ;
selectedBBoxes [ i ] . y += dy [ i ] ;
}
xform . setTranslate ( dx [ i ] , dy [ i ] ) ;
} else {
if ( i == 0 ) {
selectedBBoxes [ i ] . x += dx ;
selectedBBoxes [ i ] . y += dy ;
}
xform . setTranslate ( dx , dy ) ;
2010-02-16 23:14:04 -06:00
}
2010-09-10 15:19:23 -05:00
if ( tlist . numberOfItems ) {
tlist . insertItemBefore ( xform , 0 ) ;
} else {
tlist . appendItem ( xform ) ;
}
2010-07-20 07:59:47 -05:00
var cmd = recalculateDimensions ( selected ) ;
if ( cmd ) {
batchCmd . addSubCommand ( cmd ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
selectorManager . requestSelector ( selected ) . resize ( ) ;
2010-02-05 21:36:35 -06:00
}
}
2010-07-20 07:59:47 -05:00
if ( ! batchCmd . isEmpty ( ) ) {
if ( undoable )
2010-02-05 21:36:35 -06:00
addCommandToHistory ( batchCmd ) ;
2010-07-20 07:59:47 -05:00
call ( "changed" , selectedElements ) ;
return batchCmd ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: cloneSelectedElements
// Create deep DOM copies (clones) of all selected elements and move them slightly
// from their originals
this . cloneSelectedElements = function ( ) {
var batchCmd = new BatchCommand ( "Clone Elements" ) ;
// find all the elements selected (stop at first null)
var len = selectedElements . length ;
for ( var i = 0 ; i < len ; ++ i ) {
var elem = selectedElements [ i ] ;
if ( elem == null ) break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// use slice to quickly get the subset of elements we need
var copiedElements = selectedElements . slice ( 0 , i ) ;
this . clearSelection ( ) ;
// note that we loop in the reverse way because of the way elements are added
// to the selectedElements array (top-first)
var i = copiedElements . length ;
while ( i -- ) {
// clone each element and replace it within copiedElements
var elem = copiedElements [ i ] = copyElem ( copiedElements [ i ] ) ;
current _layer . appendChild ( elem ) ;
batchCmd . addSubCommand ( new InsertElementCommand ( elem ) ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
if ( ! batchCmd . isEmpty ( ) ) {
addToSelection ( copiedElements . reverse ( ) ) ; // Need to reverse for correct selection-adding
this . moveSelectedElements ( 20 , 20 , false ) ;
addCommandToHistory ( batchCmd ) ;
call ( "selected" , selectedElements ) ;
}
} ;
2010-03-18 10:32:47 -05:00
2010-07-20 07:59:47 -05:00
// Function: alignSelectedElements
// Aligns selected elements
//
// Parameters:
// type - String with single character indicating the alignment type
// relative_to - String that must be one of the following:
// "selected", "largest", "smallest", "page"
this . alignSelectedElements = function ( type , relative _to ) {
var bboxes = [ ] , angles = [ ] ;
var minx = Number . MAX _VALUE , maxx = Number . MIN _VALUE , miny = Number . MAX _VALUE , maxy = Number . MIN _VALUE ;
var curwidth = Number . MIN _VALUE , curheight = Number . MIN _VALUE ;
var len = selectedElements . length ;
if ( ! len ) return ;
for ( var i = 0 ; i < len ; ++ i ) {
if ( selectedElements [ i ] == null ) break ;
var elem = selectedElements [ i ] ;
bboxes [ i ] = getStrokedBBox ( [ elem ] ) ;
// now bbox is axis-aligned and handles rotation
switch ( relative _to ) {
case 'smallest' :
if ( ( type == 'l' || type == 'c' || type == 'r' ) && ( curwidth == Number . MIN _VALUE || curwidth > bboxes [ i ] . width ) ||
( type == 't' || type == 'm' || type == 'b' ) && ( curheight == Number . MIN _VALUE || curheight > bboxes [ i ] . height ) ) {
minx = bboxes [ i ] . x ;
miny = bboxes [ i ] . y ;
maxx = bboxes [ i ] . x + bboxes [ i ] . width ;
maxy = bboxes [ i ] . y + bboxes [ i ] . height ;
curwidth = bboxes [ i ] . width ;
curheight = bboxes [ i ] . height ;
}
break ;
case 'largest' :
if ( ( type == 'l' || type == 'c' || type == 'r' ) && ( curwidth == Number . MIN _VALUE || curwidth < bboxes [ i ] . width ) ||
( type == 't' || type == 'm' || type == 'b' ) && ( curheight == Number . MIN _VALUE || curheight < bboxes [ i ] . height ) ) {
minx = bboxes [ i ] . x ;
miny = bboxes [ i ] . y ;
maxx = bboxes [ i ] . x + bboxes [ i ] . width ;
maxy = bboxes [ i ] . y + bboxes [ i ] . height ;
curwidth = bboxes [ i ] . width ;
curheight = bboxes [ i ] . height ;
}
break ;
default : // 'selected'
if ( bboxes [ i ] . x < minx ) minx = bboxes [ i ] . x ;
if ( bboxes [ i ] . y < miny ) miny = bboxes [ i ] . y ;
if ( bboxes [ i ] . x + bboxes [ i ] . width > maxx ) maxx = bboxes [ i ] . x + bboxes [ i ] . width ;
if ( bboxes [ i ] . y + bboxes [ i ] . height > maxy ) maxy = bboxes [ i ] . y + bboxes [ i ] . height ;
break ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
} // loop for each element to find the bbox and adjust min/max
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
if ( relative _to == 'page' ) {
minx = 0 ;
miny = 0 ;
maxx = canvas . contentW ;
maxy = canvas . contentH ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
var dx = new Array ( len ) ;
var dy = new Array ( len ) ;
for ( var i = 0 ; i < len ; ++ i ) {
if ( selectedElements [ i ] == null ) break ;
var elem = selectedElements [ i ] ;
var bbox = bboxes [ i ] ;
dx [ i ] = 0 ;
dy [ i ] = 0 ;
switch ( type ) {
case 'l' : // left (horizontal)
dx [ i ] = minx - bbox . x ;
break ;
case 'c' : // center (horizontal)
dx [ i ] = ( minx + maxx ) / 2 - ( bbox . x + bbox . width / 2 ) ;
break ;
case 'r' : // right (horizontal)
dx [ i ] = maxx - ( bbox . x + bbox . width ) ;
break ;
case 't' : // top (vertical)
dy [ i ] = miny - bbox . y ;
break ;
case 'm' : // middle (vertical)
dy [ i ] = ( miny + maxy ) / 2 - ( bbox . y + bbox . height / 2 ) ;
break ;
case 'b' : // bottom (vertical)
dy [ i ] = maxy - ( bbox . y + bbox . height ) ;
break ;
}
}
this . moveSelectedElements ( dx , dy ) ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Group: Additional editor tools
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
this . contentW = getResolution ( ) . w ;
this . contentH = getResolution ( ) . h ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// Function: updateCanvas
// Updates the editor canvas width/height/position after a zoom has occurred
//
// Parameters:
// w - Float with the new width
// h - Float with the new height
//
// Returns:
// Object with the following values:
// * x - The canvas' new x coordinate
// * y - The canvas' new y coordinate
// * old_x - The canvas' old x coordinate
// * old_y - The canvas' old y coordinate
// * d_x - The x position difference
// * d_y - The y position difference
this . updateCanvas = function ( w , h ) {
svgroot . setAttribute ( "width" , w ) ;
svgroot . setAttribute ( "height" , h ) ;
var bg = $ ( '#canvasBackground' ) [ 0 ] ;
var old _x = svgcontent . getAttribute ( 'x' ) ;
var old _y = svgcontent . getAttribute ( 'y' ) ;
var x = ( w / 2 - this . contentW * current _zoom / 2 ) ;
var y = ( h / 2 - this . contentH * current _zoom / 2 ) ;
assignAttributes ( svgcontent , {
width : this . contentW * current _zoom ,
height : this . contentH * current _zoom ,
'x' : x ,
'y' : y ,
"viewBox" : "0 0 " + this . contentW + " " + this . contentH
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
assignAttributes ( bg , {
width : svgcontent . getAttribute ( 'width' ) ,
height : svgcontent . getAttribute ( 'height' ) ,
x : x ,
y : y
} ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
selectorManager . selectorParentGroup . setAttribute ( "transform" , "translate(" + x + "," + y + ")" ) ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
return { x : x , y : y , old _x : old _x , old _y : old _y , d _x : x - old _x , d _y : y - old _y } ;
}
// Function: setBackground
// Set the background of the editor (NOT the actual document)
//
// Parameters:
// color - String with fill color to apply
// url - URL or path to image to use
this . setBackground = function ( color , url ) {
var bg = getElem ( 'canvasBackground' ) ;
var border = $ ( bg ) . find ( 'rect' ) [ 0 ] ;
var bg _img = getElem ( 'background_image' ) ;
border . setAttribute ( 'fill' , color ) ;
if ( url ) {
if ( ! bg _img ) {
bg _img = svgdoc . createElementNS ( svgns , "image" ) ;
assignAttributes ( bg _img , {
'id' : 'background_image' ,
'width' : '100%' ,
'height' : '100%' ,
'preserveAspectRatio' : 'xMinYMin' ,
'style' : 'pointer-events:none'
} ) ;
2010-02-05 21:36:35 -06:00
}
2010-09-10 15:19:23 -05:00
setHref ( bg _img , url ) ;
2010-07-20 07:59:47 -05:00
bg . appendChild ( bg _img ) ;
} else if ( bg _img ) {
bg _img . parentNode . removeChild ( bg _img ) ;
}
}
// Function: cycleElement
// Select the next/previous element within the current layer
//
// Parameters:
// next - Boolean where true = next and false = previous element
this . cycleElement = function ( next ) {
var cur _elem = selectedElements [ 0 ] ;
var elem = false ;
var all _elems = getVisibleElements ( current _layer ) ;
if ( cur _elem == null ) {
var num = next ? all _elems . length - 1 : 0 ;
elem = all _elems [ num ] ;
} else {
var i = all _elems . length ;
while ( i -- ) {
if ( all _elems [ i ] == cur _elem ) {
var num = next ? i - 1 : i + 1 ;
if ( num >= all _elems . length ) {
num = 0 ;
} else if ( num < 0 ) {
num = all _elems . length - 1 ;
}
elem = all _elems [ num ] ;
break ;
}
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
}
clearSelection ( ) ;
addToSelection ( [ elem ] , true ) ;
call ( "selected" , selectedElements ) ;
}
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
this . clear ( ) ;
// DEPRECATED: getPrivateMethods
// Since all methods are/should be public somehow, this function should be removed
// Being able to access private methods publicly seems wrong somehow,
// but currently appears to be the best way to allow testing and provide
// access to them to plugins.
this . getPrivateMethods = function ( ) {
var obj = {
addCommandToHistory : addCommandToHistory ,
setGradient : setGradient ,
addSvgElementFromJson : addSvgElementFromJson ,
assignAttributes : assignAttributes ,
BatchCommand : BatchCommand ,
call : call ,
ChangeElementCommand : ChangeElementCommand ,
cleanupElement : cleanupElement ,
copyElem : copyElem ,
ffClone : ffClone ,
findDefs : findDefs ,
findDuplicateGradient : findDuplicateGradient ,
getElem : getElem ,
getId : getId ,
getIntersectionList : getIntersectionList ,
getMouseTarget : getMouseTarget ,
getNextId : getNextId ,
getPathBBox : getPathBBox ,
getUrlFromAttr : getUrlFromAttr ,
hasMatrixTransform : hasMatrixTransform ,
identifyLayers : identifyLayers ,
InsertElementCommand : InsertElementCommand ,
isIdentity : isIdentity ,
logMatrix : logMatrix ,
matrixMultiply : matrixMultiply ,
MoveElementCommand : MoveElementCommand ,
preventClickDefault : preventClickDefault ,
recalculateAllSelectedDimensions : recalculateAllSelectedDimensions ,
recalculateDimensions : recalculateDimensions ,
remapElement : remapElement ,
RemoveElementCommand : RemoveElementCommand ,
removeUnusedDefElems : removeUnusedDefElems ,
round : round ,
runExtensions : runExtensions ,
sanitizeSvg : sanitizeSvg ,
SelectorManager : SelectorManager ,
shortFloat : shortFloat ,
svgCanvasToString : svgCanvasToString ,
SVGEditTransformList : SVGEditTransformList ,
svgToString : svgToString ,
toString : toString ,
transformBox : transformBox ,
transformListToTransform : transformListToTransform ,
transformPoint : transformPoint ,
walkTree : walkTree
}
return obj ;
} ;
2010-02-05 21:36:35 -06:00
2010-07-20 07:59:47 -05:00
// console.log('canvas.getPrivateMethods',canvas.getPrivateMethods);
// Test support for features/bugs
( function ( ) {
// segList functions (for FF1.5 and 2.0)
var path = document . createElementNS ( svgns , 'path' ) ;
path . setAttribute ( 'd' , 'M0,0 10,10' ) ;
var seglist = path . pathSegList ;
var seg = path . createSVGPathSegLinetoAbs ( 5 , 5 ) ;
try {
seglist . replaceItem ( seg , 0 ) ;
support . pathReplaceItem = true ;
} catch ( err ) {
support . pathReplaceItem = false ;
}
2010-06-03 07:13:37 -05:00
2010-07-20 07:59:47 -05:00
try {
seglist . insertItemBefore ( seg , 0 ) ;
support . pathInsertItemBefore = true ;
} catch ( err ) {
support . pathInsertItemBefore = false ;
}
// TODO: Find better way to check support for this
support . editableText = isOpera ;
// Correct decimals on clone attributes (Opera < 10.5/win/non-en)
var rect = document . createElementNS ( svgns , 'rect' ) ;
rect . setAttribute ( 'x' , . 1 ) ;
var crect = rect . cloneNode ( false ) ;
support . goodDecimals = ( crect . getAttribute ( 'x' ) . indexOf ( ',' ) == - 1 ) ;
if ( ! support . goodDecimals ) {
$ . alert ( " NOTE : This version of Opera is known to contain bugs in SVG - edit . \ n \
Please upgrade to the < a href = 'http://opera.com' > latest version < / a > i n w h i c h t h e p r o b l e m s h a v e b e e n f i x e d . " ) ;
2010-02-05 21:36:35 -06:00
}
2010-07-20 07:59:47 -05:00
// Get correct em/ex values
var rect = document . createElementNS ( svgns , 'rect' ) ;
rect . setAttribute ( 'width' , "1em" ) ;
rect . setAttribute ( 'height' , "1ex" ) ;
svgcontent . appendChild ( rect ) ;
var bb = rect . getBBox ( ) ;
unit _types . em = bb . width ;
unit _types . ex = bb . height ;
svgcontent . removeChild ( rect ) ;
} ( ) ) ;
2010-02-27 09:49:02 -06:00
2010-07-20 07:59:47 -05:00
}