2018-05-16 00:53:27 +00:00
/* eslint-disable no-var, eqeqeq */
/* globals $, svgedit, unescape, DOMParser, ActiveXObject, getStrokedBBox, RGBColor */
2010-10-26 02:48:36 +00:00
/ * *
2010-11-05 17:00:32 +00:00
* Package : svgedit . utilities
2010-10-26 02:48:36 +00:00
*
2012-09-16 18:53:27 +00:00
* Licensed under the MIT License
2010-10-26 02:48:36 +00:00
*
* Copyright ( c ) 2010 Alexis Deveria
* Copyright ( c ) 2010 Jeff Schiller
* /
2010-10-26 16:33:44 +00:00
// Dependencies:
// 1) jQuery
2015-11-05 03:25:30 +00:00
// 2) pathseg.js
// 3) browser.js
// 4) svgtransformlist.js
// 5) units.js
2010-10-26 02:48:36 +00:00
2018-05-16 00:53:27 +00:00
( function ( undef ) {
'use strict' ;
2010-10-26 16:33:44 +00:00
2010-11-05 17:00:32 +00:00
if ( ! svgedit . utilities ) {
svgedit . utilities = { } ;
2010-10-28 06:08:45 +00:00
}
2010-10-26 02:48:36 +00:00
2010-10-29 05:09:39 +00:00
// Constants
2010-10-26 02:48:36 +00:00
// String used to encode base64.
2010-12-02 17:14:24 +00:00
var KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' ;
2013-02-16 15:02:26 +00:00
var NS = svgedit . NS ;
2010-10-26 02:48:36 +00:00
2010-11-05 15:29:30 +00: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' ;
2018-05-16 00:53:27 +00:00
var visElemsArr = visElems . split ( ',' ) ;
// var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
2010-11-05 15:29:30 +00:00
2010-12-02 17:14:24 +00:00
var editorContext _ = null ;
2011-02-04 08:02:46 +00:00
var domdoc _ = null ;
var domcontainer _ = null ;
var svgroot _ = null ;
2010-12-02 17:14:24 +00:00
2018-05-16 00:53:27 +00:00
svgedit . utilities . init = function ( editorContext ) {
2010-12-02 17:14:24 +00:00
editorContext _ = editorContext ;
2011-02-04 08:02:46 +00:00
domdoc _ = editorContext . getDOMDocument ( ) ;
domcontainer _ = editorContext . getDOMContainer ( ) ;
svgroot _ = editorContext . getSVGRoot ( ) ;
2010-12-02 17:14:24 +00:00
} ;
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.toXml
2010-10-26 02:48:36 +00:00
// Converts characters in a string to XML-friendly entities.
//
2013-02-15 17:02:24 +00:00
// Example: '&' becomes '&'
2010-10-26 02:48:36 +00:00
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
2018-05-16 00:53:27 +00:00
svgedit . utilities . toXml = function ( str ) {
2014-02-12 10:10:56 +00:00
// ' is ok in XML, but not HTML
// > does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]")
return str . replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' ) . replace ( /"/g , '"' ) . replace ( /'/ , ''' ) ;
2010-10-26 02:48:36 +00:00
} ;
2013-02-15 23:05:23 +00:00
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.fromXml
2013-02-15 23:05:23 +00:00
// Converts XML entities in a string to single characters.
2013-02-15 17:02:24 +00:00
// Example: '&' becomes '&'
2010-10-26 02:48:36 +00:00
//
// Parameters:
// str - The string to be converted
//
2013-02-15 23:05:23 +00:00
// Returns:
2010-10-26 02:48:36 +00:00
// The converted string
2018-05-16 00:53:27 +00:00
svgedit . utilities . fromXml = function ( str ) {
2010-10-26 02:48:36 +00:00
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,
2014-01-31 02:13:37 +00:00
// also precalculate the size of the array needed.
2010-10-26 02:48:36 +00:00
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.encode64
2010-10-26 02:48:36 +00:00
// Converts a string to base64
2018-05-16 00:53:27 +00:00
svgedit . utilities . encode64 = function ( input ) {
2010-10-26 02:48:36 +00:00
// base64 strings are 4/3 larger than the original string
2014-02-01 16:13:51 +00:00
input = svgedit . utilities . encodeUTF8 ( input ) ; // convert non-ASCII characters
// input = svgedit.utilities.convertToXMLReferences(input);
if ( window . btoa ) {
return window . btoa ( input ) ; // Use native if available
2018-05-16 00:53:27 +00:00
}
var output = [ ] ;
output . length = Math . floor ( ( input . length + 2 ) / 3 ) * 4 ;
2010-10-26 02:48:36 +00:00
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 ;
}
2010-10-29 05:09:39 +00:00
output [ p ++ ] = KEYSTR . charAt ( enc1 ) ;
output [ p ++ ] = KEYSTR . charAt ( enc2 ) ;
output [ p ++ ] = KEYSTR . charAt ( enc3 ) ;
output [ p ++ ] = KEYSTR . charAt ( enc4 ) ;
2010-10-26 02:48:36 +00:00
} while ( i < input . length ) ;
return output . join ( '' ) ;
} ;
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.decode64
2010-10-26 02:48:36 +00:00
// Converts a string from base64
2018-05-16 00:53:27 +00:00
svgedit . utilities . decode64 = function ( input ) {
if ( window . atob ) {
return svgedit . utilities . decodeUTF8 ( window . atob ( input ) ) ;
}
2013-02-15 17:02:24 +00:00
var output = '' ;
var chr1 , chr2 , chr3 = '' ;
var enc1 , enc2 , enc3 , enc4 = '' ;
2010-10-26 02:48:36 +00:00
var i = 0 ;
2013-02-19 11:18:04 +00:00
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
2018-05-16 00:53:27 +00:00
input = input . replace ( /[^A-Za-z0-9+/=]/g , '' ) ;
2010-10-26 02:48:36 +00:00
2013-02-19 11:18:04 +00:00
do {
2010-10-29 05:09:39 +00:00
enc1 = KEYSTR . indexOf ( input . charAt ( i ++ ) ) ;
enc2 = KEYSTR . indexOf ( input . charAt ( i ++ ) ) ;
enc3 = KEYSTR . indexOf ( input . charAt ( i ++ ) ) ;
enc4 = KEYSTR . indexOf ( input . charAt ( i ++ ) ) ;
2010-10-26 02:48:36 +00:00
chr1 = ( enc1 << 2 ) | ( enc2 >> 4 ) ;
chr2 = ( ( enc2 & 15 ) << 4 ) | ( enc3 >> 2 ) ;
chr3 = ( ( enc3 & 3 ) << 6 ) | enc4 ;
output = output + String . fromCharCode ( chr1 ) ;
if ( enc3 != 64 ) {
2013-02-19 11:18:04 +00:00
output = output + String . fromCharCode ( chr2 ) ;
2010-10-26 02:48:36 +00:00
}
if ( enc4 != 64 ) {
2013-02-19 11:18:04 +00:00
output = output + String . fromCharCode ( chr3 ) ;
2010-10-26 02:48:36 +00:00
}
2013-02-15 17:02:24 +00:00
chr1 = chr2 = chr3 = '' ;
enc1 = enc2 = enc3 = enc4 = '' ;
2013-02-19 11:18:04 +00:00
} while ( i < input . length ) ;
2018-05-16 00:53:27 +00:00
return svgedit . utilities . decodeUTF8 ( output ) ;
2014-06-12 23:42:27 +00:00
} ;
svgedit . utilities . decodeUTF8 = function ( argString ) {
2018-05-16 00:53:27 +00:00
return decodeURIComponent ( escape ( argString ) ) ;
2010-10-26 02:48:36 +00:00
} ;
2014-02-01 16:13:51 +00:00
// codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded
svgedit . utilities . encodeUTF8 = function ( argString ) {
2018-05-16 00:53:27 +00:00
return unescape ( encodeURIComponent ( argString ) ) ;
2014-02-01 16:13:51 +00:00
} ;
2010-10-26 02:48:36 +00:00
2018-04-12 18:00:52 +00:00
/ * *
* convert dataURL to object URL
* @ param { string } dataurl
* @ return { string } object URL or empty string
* /
svgedit . utilities . dataURLToObjectURL = function ( dataurl ) {
if ( typeof Uint8Array == 'undefined' || typeof Blob == 'undefined' || typeof URL == 'undefined' || ! URL . createObjectURL ) {
return '' ;
}
2018-05-16 00:53:27 +00:00
var arr = dataurl . split ( ',' ) , mime = arr [ 0 ] . match ( /:(.*?);/ ) [ 1 ] ,
bstr = atob ( arr [ 1 ] ) , n = bstr . length , u8arr = new Uint8Array ( n ) ;
while ( n -- ) {
u8arr [ n ] = bstr . charCodeAt ( n ) ;
}
var blob = new Blob ( [ u8arr ] , { type : mime } ) ;
2018-04-12 18:00:52 +00:00
return URL . createObjectURL ( blob ) ;
} ;
/ * *
* get object URL for a blob object
* @ param { Blob } blob A Blob object or File object
* @ return { string } object URL or empty string
* /
svgedit . utilities . createObjectURL = function ( blob ) {
if ( ! blob || typeof URL == 'undefined' || ! URL . createObjectURL ) {
return '' ;
}
return URL . createObjectURL ( blob ) ;
} ;
/ * *
* @ property { string } blankPageObjectURL
* /
2018-05-16 00:53:27 +00:00
svgedit . utilities . blankPageObjectURL = ( function ( ) {
2018-04-12 18:00:52 +00:00
if ( typeof Blob == 'undefined' ) {
return '' ;
}
2018-05-16 00:53:27 +00:00
var blob = new Blob ( [ '<html><head><title>SVG-edit</title></head><body> </body></html>' ] , { type : 'text/html' } ) ;
2018-04-12 18:00:52 +00:00
return svgedit . utilities . createObjectURL ( blob ) ;
2018-05-16 00:53:27 +00:00
} ) ( ) ;
2018-04-12 18:00:52 +00:00
2013-02-15 23:05:23 +00:00
// Function: svgedit.utilities.convertToXMLReferences
2010-10-26 02:48:36 +00:00
// Converts a string to use XML references
2018-05-16 00:53:27 +00:00
svgedit . utilities . convertToXMLReferences = function ( input ) {
2014-01-31 02:13:37 +00:00
var n ,
output = '' ;
2018-05-16 00:53:27 +00:00
for ( n = 0 ; n < input . length ; n ++ ) {
2010-10-26 02:48:36 +00:00
var c = input . charCodeAt ( n ) ;
if ( c < 128 ) {
output += input [ n ] ;
2018-05-16 00:53:27 +00:00
} else if ( c > 127 ) {
2013-02-15 17:02:24 +00:00
output += ( '&#' + c + ';' ) ;
2010-10-26 02:48:36 +00:00
}
}
return output ;
} ;
2011-02-02 17:28:43 +00:00
// Function: svgedit.utilities.text2xml
2010-10-26 02:48:36 +00:00
// 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
2018-05-16 00:53:27 +00:00
svgedit . utilities . text2xml = function ( sXML ) {
if ( sXML . indexOf ( '<svg:svg' ) >= 0 ) {
2010-10-26 02:48:36 +00:00
sXML = sXML . replace ( /<(\/?)svg:/g , '<$1' ) . replace ( 'xmlns:svg' , 'xmlns' ) ;
}
2014-01-31 02:13:37 +00:00
var out , dXML ;
2018-05-16 00:53:27 +00:00
try {
dXML = ( window . DOMParser ) ? new DOMParser ( ) : new ActiveXObject ( 'Microsoft.XMLDOM' ) ;
2010-10-26 02:48:36 +00:00
dXML . async = false ;
2018-05-16 00:53:27 +00:00
} catch ( e ) {
2013-02-15 23:05:23 +00:00
throw new Error ( 'XML Parser could not be instantiated' ) ;
2013-02-19 11:18:04 +00:00
}
2018-05-16 00:53:27 +00:00
try {
2014-01-31 02:13:37 +00:00
if ( dXML . loadXML ) {
out = ( dXML . loadXML ( sXML ) ) ? dXML : false ;
2018-05-16 00:53:27 +00:00
} else {
2014-01-31 02:13:37 +00:00
out = dXML . parseFromString ( sXML , 'text/xml' ) ;
}
2018-05-16 00:53:27 +00:00
} catch ( e2 ) { throw new Error ( 'Error parsing XML string' ) ; }
2010-10-26 02:48:36 +00:00
return out ;
} ;
2011-02-02 17:28:43 +00:00
// Function: svgedit.utilities.bboxToObj
2010-10-26 02:48:36 +00:00
// Converts a SVGRect into an object.
2018-05-16 00:53:27 +00:00
//
2010-10-26 02:48:36 +00:00
// Parameters:
// bbox - a SVGRect
2018-05-16 00:53:27 +00:00
//
2010-10-26 02:48:36 +00:00
// Returns:
// An object with properties names x, y, width, height.
2018-05-16 00:53:27 +00:00
svgedit . utilities . bboxToObj = function ( bbox ) {
2010-10-26 02:48:36 +00:00
return {
x : bbox . x ,
y : bbox . y ,
width : bbox . width ,
height : bbox . height
2013-02-19 11:18:04 +00:00
} ;
2010-10-26 02:48:36 +00:00
} ;
2010-10-26 16:33:44 +00:00
2011-02-02 17:28:43 +00:00
// Function: svgedit.utilities.walkTree
2010-10-26 17:11:23 +00:00
// 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
2018-05-16 00:53:27 +00:00
svgedit . utilities . walkTree = function ( elem , cbFn ) {
if ( elem && elem . nodeType === 1 ) {
2010-10-26 17:11:23 +00:00
cbFn ( elem ) ;
var i = elem . childNodes . length ;
while ( i -- ) {
2010-11-05 17:00:32 +00:00
svgedit . utilities . walkTree ( elem . childNodes . item ( i ) , cbFn ) ;
2010-10-26 17:11:23 +00:00
}
}
} ;
2011-02-02 17:28:43 +00:00
// Function: svgedit.utilities.walkTreePost
2010-10-26 17:11:23 +00:00
// Walks the tree and executes the callback on each element in a depth-first fashion
// TODO: FIXME: Shouldn't this be calling walkTreePost?
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
2018-05-16 00:53:27 +00:00
svgedit . utilities . walkTreePost = function ( elem , cbFn ) {
2010-10-26 17:11:23 +00:00
if ( elem && elem . nodeType == 1 ) {
var i = elem . childNodes . length ;
while ( i -- ) {
2010-11-05 17:00:32 +00:00
svgedit . utilities . walkTree ( elem . childNodes . item ( i ) , cbFn ) ;
2010-10-26 17:11:23 +00:00
}
cbFn ( elem ) ;
}
} ;
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.getUrlFromAttr
2013-02-15 23:05:23 +00:00
// Extracts the URL from the url(...) syntax of some attributes.
2010-10-26 17:11:23 +00:00
// Three variants:
2013-02-19 11:18:04 +00:00
// * <circle fill="url(someFile.svg#foo)" />
2010-10-26 17:11:23 +00:00
// * <circle fill="url('someFile.svg#foo')" />
// * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
2013-02-15 23:05:23 +00:00
//
2010-10-26 17:11:23 +00:00
// Returns:
// String with just the URL, like someFile.svg#foo
2018-05-16 00:53:27 +00:00
svgedit . utilities . getUrlFromAttr = function ( attrVal ) {
2013-02-15 23:05:23 +00:00
if ( attrVal ) {
2010-10-26 17:11:23 +00:00
// url("#somegrad")
if ( attrVal . indexOf ( 'url("' ) === 0 ) {
2018-05-16 00:53:27 +00:00
return attrVal . substring ( 5 , attrVal . indexOf ( '"' , 6 ) ) ;
2010-10-26 17:11:23 +00:00
}
// url('#somegrad')
2014-01-31 02:13:37 +00:00
if ( attrVal . indexOf ( "url('" ) === 0 ) {
2018-05-16 00:53:27 +00:00
return attrVal . substring ( 5 , attrVal . indexOf ( "'" , 6 ) ) ;
2010-10-26 17:11:23 +00:00
}
2018-05-16 00:53:27 +00:00
if ( attrVal . indexOf ( 'url(' ) === 0 ) {
2013-02-15 16:51:48 +00:00
return attrVal . substring ( 4 , attrVal . indexOf ( ')' ) ) ;
2010-10-26 17:11:23 +00:00
}
}
return null ;
} ;
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.getHref
2010-10-28 05:50:39 +00:00
// Returns the given element's xlink:href value
2018-05-16 00:53:27 +00:00
svgedit . utilities . getHref = function ( elem ) {
2013-02-16 15:02:26 +00:00
return elem . getAttributeNS ( NS . XLINK , 'href' ) ;
2013-02-19 11:18:04 +00:00
} ;
2010-10-28 05:50:39 +00:00
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.setHref
2010-10-28 05:50:39 +00:00
// Sets the given element's xlink:href value
2018-05-16 00:53:27 +00:00
svgedit . utilities . setHref = function ( elem , val ) {
2013-02-16 15:02:26 +00:00
elem . setAttributeNS ( NS . XLINK , 'xlink:href' , val ) ;
2013-02-19 11:18:04 +00:00
} ;
2010-10-28 05:50:39 +00:00
// Function: findDefs
//
// Returns:
// The document's <defs> element, create it first if necessary
2018-05-16 00:53:27 +00:00
svgedit . utilities . findDefs = function ( ) {
2013-02-14 19:11:30 +00:00
var svgElement = editorContext _ . getSVGContent ( ) ;
2013-02-16 15:02:26 +00:00
var defs = svgElement . getElementsByTagNameNS ( NS . SVG , 'defs' ) ;
2010-10-28 05:50:39 +00:00
if ( defs . length > 0 ) {
defs = defs [ 0 ] ;
2013-02-15 23:05:23 +00:00
} else {
2013-02-16 15:02:26 +00:00
defs = svgElement . ownerDocument . createElementNS ( NS . SVG , 'defs' ) ;
2013-02-14 15:19:46 +00:00
if ( svgElement . firstChild ) {
// first child is a comment, so call nextSibling
svgElement . insertBefore ( defs , svgElement . firstChild . nextSibling ) ;
} else {
svgElement . appendChild ( defs ) ;
}
2010-10-28 05:50:39 +00:00
}
return defs ;
} ;
2010-11-08 08:42:46 +00:00
// TODO(codedread): Consider moving the next to functions to bbox.js
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.getPathBBox
2010-11-05 15:29:30 +00:00
// 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
2013-02-15 23:05:23 +00:00
//
2010-11-05 15:29:30 +00:00
// Parameters:
// path - The path DOM element to get the BBox for
//
// Returns:
// A BBox-like object
2018-05-16 00:53:27 +00:00
svgedit . utilities . getPathBBox = function ( path ) {
2010-11-05 15:29:30 +00:00
var seglist = path . pathSegList ;
var tot = seglist . numberOfItems ;
2013-02-15 23:05:23 +00:00
2010-11-05 15:29:30 +00:00
var bounds = [ [ ] , [ ] ] ;
var start = seglist . getItem ( 0 ) ;
var P0 = [ start . x , start . y ] ;
2012-03-30 03:50:54 +00:00
2014-01-31 02:13:37 +00:00
var i ;
for ( i = 0 ; i < tot ; i ++ ) {
2010-11-05 15:29:30 +00:00
var seg = seglist . getItem ( i ) ;
2012-03-30 03:50:54 +00:00
2018-05-16 00:53:27 +00:00
if ( seg . x === undef ) { continue ; }
2012-03-30 03:50:54 +00:00
2010-11-05 15:29:30 +00:00
// Add actual points to limits
bounds [ 0 ] . push ( P0 [ 0 ] ) ;
bounds [ 1 ] . push ( P0 [ 1 ] ) ;
2013-02-15 23:05:23 +00:00
2014-05-22 10:21:29 +00:00
if ( seg . x1 ) {
2010-11-05 15:29:30 +00:00
var P1 = [ seg . x1 , seg . y1 ] ,
P2 = [ seg . x2 , seg . y2 ] ,
P3 = [ seg . x , seg . y ] ;
2014-01-31 02:13:37 +00:00
var j ;
for ( j = 0 ; j < 2 ; j ++ ) {
2018-05-16 00:53:27 +00:00
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 ] ;
2010-11-05 15:29:30 +00:00
} ;
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 ] ;
2013-02-15 23:05:23 +00:00
2014-01-31 02:13:37 +00:00
if ( a == 0 ) {
if ( b == 0 ) {
2010-11-05 15:29:30 +00:00
continue ;
}
var t = - c / b ;
2018-05-16 00:53:27 +00:00
if ( t > 0 && t < 1 ) {
2010-11-05 15:29:30 +00:00
bounds [ j ] . push ( calc ( t ) ) ;
}
continue ;
}
2018-05-16 00:53:27 +00:00
var b2ac = Math . pow ( b , 2 ) - 4 * c * a ;
if ( b2ac < 0 ) { continue ; }
var t1 = ( - b + Math . sqrt ( b2ac ) ) / ( 2 * a ) ;
if ( t1 > 0 && t1 < 1 ) { bounds [ j ] . push ( calc ( t1 ) ) ; }
var t2 = ( - b - Math . sqrt ( b2ac ) ) / ( 2 * a ) ;
if ( t2 > 0 && t2 < 1 ) { bounds [ j ] . push ( calc ( t2 ) ) ; }
2010-11-05 15:29:30 +00:00
}
P0 = P3 ;
} else {
bounds [ 0 ] . push ( seg . x ) ;
bounds [ 1 ] . push ( seg . y ) ;
}
}
2013-02-15 23:05:23 +00:00
2010-11-05 15:29:30 +00:00
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
} ;
} ;
2011-02-09 21:32:53 +00:00
// Function: groupBBFix
// Get the given/selected element's bounding box object, checking for
// horizontal/vertical lines (see issue 717)
// Note that performance is currently terrible, so some way to improve would
// be great.
//
2013-02-15 23:05:23 +00:00
// Parameters:
2011-02-09 21:32:53 +00:00
// selected - Container or <use> DOM element
2018-05-16 00:53:27 +00:00
function groupBBFix ( selected ) {
if ( svgedit . browser . supportsHVLineContainerBBox ( ) ) {
try { return selected . getBBox ( ) ; } catch ( e ) { }
2011-02-09 21:32:53 +00:00
}
var ref = $ . data ( selected , 'ref' ) ;
var matched = null ;
2014-01-31 02:13:37 +00:00
var ret , copy ;
2013-02-15 23:05:23 +00:00
2018-05-16 00:53:27 +00:00
if ( ref ) {
2014-01-31 02:13:37 +00:00
copy = $ ( ref ) . children ( ) . clone ( ) . attr ( 'visibility' , 'hidden' ) ;
2011-02-09 21:32:53 +00:00
$ ( svgroot _ ) . append ( copy ) ;
matched = copy . filter ( 'line, path' ) ;
} else {
matched = $ ( selected ) . find ( 'line, path' ) ;
}
2013-02-15 23:05:23 +00:00
2011-02-09 21:32:53 +00:00
var issue = false ;
2018-05-16 00:53:27 +00:00
if ( matched . length ) {
matched . each ( function ( ) {
2011-02-09 21:32:53 +00:00
var bb = this . getBBox ( ) ;
2018-05-16 00:53:27 +00:00
if ( ! bb . width || ! bb . height ) {
2011-02-09 21:32:53 +00:00
issue = true ;
}
} ) ;
2018-05-16 00:53:27 +00:00
if ( issue ) {
2011-02-09 21:32:53 +00:00
var elems = ref ? copy : $ ( selected ) . children ( ) ;
2014-01-31 02:13:37 +00:00
ret = getStrokedBBox ( elems ) ; // getStrokedBBox defined in svgcanvas
2011-02-09 21:32:53 +00:00
} else {
ret = selected . getBBox ( ) ;
}
} else {
ret = selected . getBBox ( ) ;
}
2018-05-16 00:53:27 +00:00
if ( ref ) {
2011-02-09 21:32:53 +00:00
copy . remove ( ) ;
}
return ret ;
}
2010-11-05 17:00:32 +00:00
// Function: svgedit.utilities.getBBox
2010-11-05 15:29:30 +00:00
// 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
2018-05-16 00:53:27 +00:00
svgedit . utilities . getBBox = function ( elem ) {
2010-12-02 17:14:24 +00:00
var selected = elem || editorContext _ . geSelectedElements ( ) [ 0 ] ;
2018-05-16 00:53:27 +00:00
if ( elem . nodeType !== 1 ) { return null ; }
2010-11-05 15:29:30 +00:00
var ret = null ;
var elname = selected . nodeName ;
2013-02-15 23:05:23 +00:00
2018-05-16 00:53:27 +00:00
switch ( elname ) {
2011-02-09 16:30:11 +00:00
case 'text' :
2018-05-16 00:53:27 +00:00
if ( selected . textContent === '' ) {
2011-02-09 16:30:11 +00:00
selected . textContent = 'a' ; // Some character needed for the selector to use.
ret = selected . getBBox ( ) ;
selected . textContent = '' ;
} else {
2015-12-01 03:08:13 +00:00
if ( selected . getBBox ) { ret = selected . getBBox ( ) ; }
2011-02-09 16:30:11 +00:00
}
break ;
case 'path' :
2018-05-16 00:53:27 +00:00
if ( ! svgedit . browser . supportsPathBBox ( ) ) {
2011-02-09 16:30:11 +00:00
ret = svgedit . utilities . getPathBBox ( selected ) ;
} else {
2015-12-01 03:08:13 +00:00
if ( selected . getBBox ) { ret = selected . getBBox ( ) ; }
2011-02-09 16:30:11 +00:00
}
break ;
case 'g' :
case 'a' :
2011-02-09 21:32:53 +00:00
ret = groupBBFix ( selected ) ;
2011-02-09 16:30:11 +00:00
break ;
default :
2011-02-09 21:32:53 +00:00
2018-05-16 00:53:27 +00:00
if ( elname === 'use' ) {
2011-02-09 21:32:53 +00:00
ret = groupBBFix ( selected , true ) ;
}
2018-05-16 00:53:27 +00:00
if ( elname === 'use' || ( elname === 'foreignObject' && svgedit . browser . isWebkit ( ) ) ) {
if ( ! ret ) { ret = selected . getBBox ( ) ; }
2012-11-28 05:39:48 +00:00
// This is resolved in later versions of webkit, perhaps we should
// have a featured detection for correct 'use' behavior?
// ——————————
2018-05-16 00:53:27 +00:00
if ( ! svgedit . browser . isWebkit ( ) ) {
2011-02-23 15:56:14 +00:00
var bb = { } ;
bb . width = ret . width ;
bb . height = ret . height ;
2018-05-16 00:53:27 +00:00
bb . x = ret . x + parseFloat ( selected . getAttribute ( 'x' ) || 0 ) ;
bb . y = ret . y + parseFloat ( selected . getAttribute ( 'y' ) || 0 ) ;
2011-02-23 15:56:14 +00:00
ret = bb ;
2015-12-11 06:34:03 +00:00
}
2018-05-16 00:53:27 +00:00
} else if ( ~ visElemsArr . indexOf ( elname ) ) {
if ( selected ) {
ret = selected . getBBox ( ) ;
} else {
2011-02-09 16:30:11 +00:00
// Check if element is child of a foreignObject
2013-02-15 17:02:24 +00:00
var fo = $ ( selected ) . closest ( 'foreignObject' ) ;
2015-12-01 03:08:13 +00:00
if ( fo . length ) {
if ( fo [ 0 ] . getBBox ) {
2011-02-09 16:30:11 +00:00
ret = fo [ 0 ] . getBBox ( ) ;
}
}
}
}
}
2018-05-16 00:53:27 +00:00
if ( ret ) {
2011-02-09 16:30:11 +00:00
ret = svgedit . utilities . bboxToObj ( ret ) ;
}
// get the bounding box from the DOM (which is in that element's coordinate system)
return ret ;
2010-11-05 15:29:30 +00:00
} ;
2016-04-26 20:01:39 +00:00
// Function: getPathDFromSegments
// Create a path 'd' attribute from path segments.
// Each segment is an array of the form: [singleChar, [x,y, x,y, ...]]
//
// Parameters:
// pathSegments - An array of path segments to be converted
//
// Returns:
// The converted path d attribute.
2018-05-16 00:53:27 +00:00
svgedit . utilities . getPathDFromSegments = function ( pathSegments ) {
2016-04-22 16:24:52 +00:00
var d = '' ;
2018-05-16 00:53:27 +00:00
$ . each ( pathSegments , function ( j , seg ) {
2016-04-22 16:24:52 +00:00
var i ;
2016-04-26 20:01:39 +00:00
var pts = seg [ 1 ] ;
d += seg [ 0 ] ;
2018-05-16 00:53:27 +00:00
for ( i = 0 ; i < pts . length ; i += 2 ) {
d += ( pts [ i ] + ',' + pts [ i + 1 ] ) + ' ' ;
2016-04-22 16:24:52 +00:00
}
} ) ;
2016-04-26 20:01:39 +00:00
return d ;
} ;
2016-04-22 16:24:52 +00:00
2016-04-26 20:01:39 +00:00
// Function: getPathDFromElement
// Make a path 'd' attribute from a simple SVG element shape.
//
// Parameters:
// elem - The element to be converted
//
// Returns:
// The path d attribute or undefined if the element type is unknown.
2018-05-16 00:53:27 +00:00
svgedit . utilities . getPathDFromElement = function ( elem ) {
2016-04-22 16:24:52 +00:00
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81 ;
var d , a , rx , ry ;
switch ( elem . tagName ) {
2018-05-16 00:53:27 +00:00
case 'ellipse' :
case 'circle' :
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' ) ;
}
2016-04-22 16:24:52 +00:00
2018-05-16 00:53:27 +00:00
d = svgedit . utilities . getPathDFromSegments ( [
[ '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' :
a = $ ( elem ) . attr ( [ 'x1' , 'y1' , 'x2' , 'y2' ] ) ;
d = 'M' + a . x1 + ',' + a . y1 + 'L' + a . x2 + ',' + a . y2 ;
break ;
case 'polyline' :
d = 'M' + elem . getAttribute ( 'points' ) ;
break ;
case 'polygon' :
d = 'M' + elem . getAttribute ( 'points' ) + ' Z' ;
break ;
case 'rect' :
var r = $ ( elem ) . attr ( [ 'rx' , 'ry' ] ) ;
rx = r . rx ;
ry = r . ry ;
var b = elem . getBBox ( ) ;
var x = b . x , y = b . y , w = b . width , h = b . height ;
num = 4 - num ; // Why? Because!
if ( ! rx && ! ry ) {
// Regular rect
2016-04-22 16:24:52 +00:00
d = svgedit . utilities . getPathDFromSegments ( [
2018-05-16 00:53:27 +00:00
[ 'M' , [ x , y ] ] ,
[ 'L' , [ x + w , y ] ] ,
[ 'L' , [ x + w , y + h ] ] ,
[ 'L' , [ x , y + h ] ] ,
[ 'L' , [ x , y ] ] ,
[ 'Z' , [ ] ]
2016-04-22 16:24:52 +00:00
] ) ;
2018-05-16 00:53:27 +00:00
} else {
d = svgedit . utilities . getPathDFromSegments ( [
[ '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' , [ ] ]
] ) ;
}
break ;
default :
break ;
2016-04-22 16:24:52 +00:00
}
return d ;
} ;
2016-04-26 20:01:39 +00:00
// Function: getExtraAttributesForConvertToPath
// Get a set of attributes from an element that is useful for convertToPath.
//
// Parameters:
// elem - The element to be probed
//
// Returns:
// An object with attributes.
2018-05-16 00:53:27 +00:00
svgedit . utilities . getExtraAttributesForConvertToPath = function ( elem ) {
var attrs = { } ;
2016-04-22 16:24:52 +00:00
// TODO: make this list global so that we can properly maintain it
// TODO: what about @transform, @clip-rule, @fill-rule, etc?
2018-05-16 00:53:27 +00:00
$ . each ( [ 'marker-start' , 'marker-end' , 'marker-mid' , 'filter' , 'clip-path' ] , function ( ) {
2016-04-26 20:01:39 +00:00
var a = elem . getAttribute ( this ) ;
if ( a ) {
attrs [ this ] = a ;
2016-04-22 16:24:52 +00:00
}
} ) ;
2016-04-26 20:01:39 +00:00
return attrs ;
2016-04-22 16:24:52 +00:00
} ;
// Function: getBBoxOfElementAsPath
// Get the BBox of an element-as-path
//
// Parameters:
// elem - The DOM element to be probed
2016-04-26 20:01:39 +00:00
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
2016-04-22 16:24:52 +00:00
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
//
// Returns:
// The resulting path's bounding box object.
2018-05-16 00:53:27 +00:00
svgedit . utilities . getBBoxOfElementAsPath = function ( elem , addSvgElementFromJson , pathActions ) {
2016-04-22 16:24:52 +00:00
var path = addSvgElementFromJson ( {
'element' : 'path' ,
2016-04-26 20:01:39 +00:00
'attr' : svgedit . utilities . getExtraAttributesForConvertToPath ( elem )
2016-04-22 16:24:52 +00:00
} ) ;
var eltrans = elem . getAttribute ( 'transform' ) ;
if ( eltrans ) {
path . setAttribute ( 'transform' , eltrans ) ;
}
var parent = elem . parentNode ;
if ( elem . nextSibling ) {
parent . insertBefore ( path , elem ) ;
} else {
parent . appendChild ( path ) ;
}
2016-04-26 20:01:39 +00:00
var d = svgedit . utilities . getPathDFromElement ( elem ) ;
2018-05-16 00:53:27 +00:00
if ( d ) path . setAttribute ( 'd' , d ) ;
else path . parentNode . removeChild ( path ) ;
2016-04-22 16:24:52 +00:00
// Get the correct BBox of the new path, then discard it
pathActions . resetOrientation ( path ) ;
var bb = false ;
try {
bb = path . getBBox ( ) ;
2018-05-16 00:53:27 +00:00
} catch ( e ) {
2016-04-22 16:24:52 +00:00
// Firefox fails
}
path . parentNode . removeChild ( path ) ;
return bb ;
2016-04-26 20:01:39 +00:00
} ;
2016-04-22 16:24:52 +00:00
2016-04-22 17:36:10 +00:00
// Function: convertToPath
2016-04-22 16:24:52 +00:00
// Convert selected element to a path.
//
// Parameters:
// elem - The DOM element to be converted
// attrs - Apply attributes to new path. see canvas.convertToPath
2016-04-26 20:01:39 +00:00
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
2016-04-22 16:24:52 +00:00
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
// clearSelection - see canvas.clearSelection
// addToSelection - see canvas.addToSelection
// history - see svgedit.history
// addCommandToHistory - see canvas.addCommandToHistory
//
// Returns:
2016-04-22 17:39:20 +00:00
// The converted path element or null if the DOM element was not recognized.
2018-05-16 00:53:27 +00:00
svgedit . utilities . convertToPath = function ( elem , attrs , addSvgElementFromJson , pathActions , clearSelection , addToSelection , history , addCommandToHistory ) {
2016-04-22 16:24:52 +00:00
var batchCmd = new history . BatchCommand ( 'Convert element to Path' ) ;
2016-04-26 20:01:39 +00:00
// Any attribute on the element not covered by the passed-in attributes
attrs = $ . extend ( { } , attrs , svgedit . utilities . getExtraAttributesForConvertToPath ( elem ) ) ;
2016-04-22 16:24:52 +00:00
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 ) ;
}
2016-04-26 20:01:39 +00:00
var d = svgedit . utilities . getPathDFromElement ( elem ) ;
if ( d ) {
2016-04-22 16:24:52 +00:00
path . setAttribute ( 'd' , d ) ;
// Replace the current element with the converted one
// Reorient if it has a matrix
if ( eltrans ) {
var tlist = svgedit . transformlist . getTransformList ( path ) ;
if ( svgedit . math . hasMatrixTransform ( tlist ) ) {
pathActions . resetOrientation ( path ) ;
}
}
var nextSibling = elem . nextSibling ;
batchCmd . addSubCommand ( new history . RemoveElementCommand ( elem , nextSibling , parent ) ) ;
batchCmd . addSubCommand ( new history . InsertElementCommand ( path ) ) ;
clearSelection ( ) ;
elem . parentNode . removeChild ( elem ) ;
path . setAttribute ( 'id' , id ) ;
path . removeAttribute ( 'visibility' ) ;
addToSelection ( [ path ] , true ) ;
addCommandToHistory ( batchCmd ) ;
return path ;
} else {
// the elem.tagName was not recognized, so no "d" attribute. Remove it, so we've haven't changed anything.
path . parentNode . removeChild ( path ) ;
return null ;
}
} ;
2016-04-28 16:11:54 +00:00
// Function: bBoxCanBeOptimizedOverNativeGetBBox
// Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when
// the rotation angle is a multiple of 90 degrees and there are no complex transforms.
// Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it.
//
// The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated
// about it's center.
//
// The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses
// that width and height, and applies any transforms to get the final bbox. This means the calculated bbox
// is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the
// same bbox.
//
// The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call
// getBBox then apply the angle and any transforms.
//
// Parameters:
// angle - The rotation angle in degrees
// hasMatrixTransform - True if there is a matrix transform
//
// Returns:
// True if the bbox can be optimized.
2018-05-16 00:53:27 +00:00
function bBoxCanBeOptimizedOverNativeGetBBox ( angle , hasMatrixTransform ) {
2016-04-28 16:11:54 +00:00
var angleModulo90 = angle % 90 ;
var closeTo90 = angleModulo90 < - 89.99 || angleModulo90 > 89.99 ;
var closeTo0 = angleModulo90 > - 0.001 && angleModulo90 < 0.001 ;
2018-05-16 00:53:27 +00:00
return hasMatrixTransform || ! ( closeTo0 || closeTo90 ) ;
2016-04-28 16:11:54 +00:00
}
2016-04-24 20:43:20 +00:00
// Function: getBBoxWithTransform
// Get bounding box that includes any transforms.
//
// Parameters:
// elem - The DOM element to be converted
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
//
// Returns:
// A single bounding box object
2018-05-16 00:53:27 +00:00
svgedit . utilities . getBBoxWithTransform = function ( elem , addSvgElementFromJson , pathActions ) {
2016-04-24 20:43:20 +00:00
// 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).
2016-04-22 16:24:52 +00:00
2016-04-24 20:43:20 +00:00
var bb = svgedit . utilities . getBBox ( elem ) ;
if ( ! bb ) {
return null ;
}
2016-04-26 20:01:39 +00:00
var tlist = svgedit . transformlist . getTransformList ( elem ) ;
2016-04-24 20:43:20 +00:00
var angle = svgedit . utilities . getRotationAngleFromTransformList ( tlist ) ;
2016-04-28 16:11:54 +00:00
var hasMatrixTransform = svgedit . math . hasMatrixTransform ( tlist ) ;
2016-04-24 20:43:20 +00:00
2016-04-28 16:11:54 +00:00
if ( angle || hasMatrixTransform ) {
2018-05-16 00:53:27 +00:00
var goodBb = false ;
2016-04-28 16:11:54 +00:00
if ( bBoxCanBeOptimizedOverNativeGetBBox ( angle , hasMatrixTransform ) ) {
// Get the BBox from the raw path for these elements
// TODO: why ellipse and not circle
var elemNames = [ 'ellipse' , 'path' , 'line' , 'polyline' , 'polygon' ] ;
if ( elemNames . indexOf ( elem . tagName ) >= 0 ) {
2018-05-16 00:53:27 +00:00
bb = goodBb = svgedit . utilities . getBBoxOfElementAsPath ( elem , addSvgElementFromJson , pathActions ) ;
2016-04-28 16:11:54 +00:00
} else if ( elem . tagName == 'rect' ) {
// Look for radius
var rx = elem . getAttribute ( 'rx' ) ;
var ry = elem . getAttribute ( 'ry' ) ;
if ( rx || ry ) {
2018-05-16 00:53:27 +00:00
bb = goodBb = svgedit . utilities . getBBoxOfElementAsPath ( elem , addSvgElementFromJson , pathActions ) ;
2016-04-28 16:11:54 +00:00
}
2016-04-24 20:43:20 +00:00
}
}
2018-05-16 00:53:27 +00:00
if ( ! goodBb ) {
2016-04-26 20:01:39 +00:00
var matrix = svgedit . math . transformListToTransform ( tlist ) . matrix ;
2016-04-24 20:43:20 +00:00
bb = svgedit . math . transformBox ( bb . x , bb . y , bb . width , bb . height , matrix ) . aabox ;
// Old technique that was exceedingly slow with large documents.
//
// Accurate way to get BBox of rotated element in Firefox:
// Put element in group and get its BBox
//
// Must use clone else FF freaks out
2018-05-16 00:53:27 +00:00
// var clone = elem.cloneNode(true);
// var g = document.createElementNS(NS.SVG, 'g');
// var parent = elem.parentNode;
// parent.appendChild(g);
// g.appendChild(clone);
// var bb2 = svgedit.utilities.bboxToObj(g.getBBox());
// parent.removeChild(g);
2016-04-24 20:43:20 +00:00
}
}
return bb ;
} ;
// TODO: This is problematic with large stroke-width and, for example, a single horizontal line. The calculated BBox extends way beyond left and right sides.
2018-05-16 00:53:27 +00:00
function getStrokeOffsetForBBox ( elem ) {
2016-04-24 20:43:20 +00:00
var sw = elem . getAttribute ( 'stroke-width' ) ;
2018-05-16 00:53:27 +00:00
return ( ! isNaN ( sw ) && elem . getAttribute ( 'stroke' ) != 'none' ) ? sw / 2 : 0 ;
2016-04-24 20:43:20 +00:00
} ;
// Function: getStrokedBBox
// Get the bounding box for one or more stroked and/or transformed elements
2010-11-05 15:59:30 +00:00
//
// Parameters:
2016-04-24 20:43:20 +00:00
// elems - Array with DOM elements to check
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
//
// Returns:
// A single bounding box object
2018-05-16 00:53:27 +00:00
svgedit . utilities . getStrokedBBox = function ( elems , addSvgElementFromJson , pathActions ) {
if ( ! elems || ! elems . length ) { return false ; }
var fullBb ;
$ . each ( elems , function ( ) {
if ( fullBb ) { return ; }
if ( ! this . parentNode ) { return ; }
fullBb = svgedit . utilities . getBBoxWithTransform ( this , addSvgElementFromJson , pathActions ) ;
2016-04-24 20:43:20 +00:00
} ) ;
// This shouldn't ever happen...
2018-05-16 00:53:27 +00:00
if ( fullBb === undefined ) { return null ; }
2016-04-24 20:43:20 +00:00
2018-05-16 00:53:27 +00:00
// fullBb doesn't include the stoke, so this does no good!
// if (elems.length == 1) return fullBb;
2016-04-24 20:43:20 +00:00
2018-05-16 00:53:27 +00:00
var maxX = fullBb . x + fullBb . width ;
var maxY = fullBb . y + fullBb . height ;
var minX = fullBb . x ;
var minY = fullBb . y ;
2016-04-24 20:43:20 +00:00
// If only one elem, don't call the potentially slow getBBoxWithTransform method again.
2016-04-26 20:01:39 +00:00
if ( elems . length === 1 ) {
2016-04-24 20:43:20 +00:00
var offset = getStrokeOffsetForBBox ( elems [ 0 ] ) ;
2018-05-16 00:53:27 +00:00
minX -= offset ;
minY -= offset ;
maxX += offset ;
maxY += offset ;
2016-04-24 20:43:20 +00:00
} else {
2018-05-16 00:53:27 +00:00
$ . each ( elems , function ( i , elem ) {
var curBb = svgedit . utilities . getBBoxWithTransform ( elem , addSvgElementFromJson , pathActions ) ;
if ( curBb ) {
2016-04-24 20:43:20 +00:00
var offset = getStrokeOffsetForBBox ( elem ) ;
2018-05-16 00:53:27 +00:00
minX = Math . min ( minX , curBb . x - offset ) ;
minY = Math . min ( minY , curBb . y - offset ) ;
2016-04-24 20:43:20 +00:00
// TODO: The old code had this test for max, but not min. I suspect this test should be for both min and max
if ( elem . nodeType == 1 ) {
2018-05-16 00:53:27 +00:00
maxX = Math . max ( maxX , curBb . x + curBb . width + offset ) ;
maxY = Math . max ( maxY , curBb . y + curBb . height + offset ) ;
2016-04-24 20:43:20 +00:00
}
}
} ) ;
}
2018-05-16 00:53:27 +00:00
fullBb . x = minX ;
fullBb . y = minY ;
fullBb . width = maxX - minX ;
fullBb . height = maxY - minY ;
return fullBb ;
2016-04-24 20:43:20 +00:00
} ;
// Function: svgedit.utilities.getRotationAngleFromTransformList
// Get the rotation angle of the given transform list.
//
// Parameters:
// tlist - List of transforms
2018-05-16 00:53:27 +00:00
// toRad - Boolean that when true returns the value in radians rather than degrees
2010-11-05 15:59:30 +00:00
//
// Returns:
// Float with the angle in degrees or radians
2018-05-16 00:53:27 +00:00
svgedit . utilities . getRotationAngleFromTransformList = function ( tlist , toRad ) {
if ( ! tlist ) { return 0 ; } // <svg> elements have no tlist
2010-11-05 15:59:30 +00:00
var N = tlist . numberOfItems ;
2014-01-31 02:13:37 +00:00
var i ;
for ( i = 0 ; i < N ; ++ i ) {
2010-11-05 15:59:30 +00:00
var xform = tlist . getItem ( i ) ;
if ( xform . type == 4 ) {
2018-05-16 00:53:27 +00:00
return toRad ? xform . angle * Math . PI / 180.0 : xform . angle ;
2010-11-05 15:59:30 +00:00
}
}
return 0.0 ;
} ;
2010-11-05 15:29:30 +00:00
2016-04-24 20:43:20 +00:00
// Function: svgedit.utilities.getRotationAngle
// Get the rotation angle of the given/selected DOM element
//
// Parameters:
// elem - Optional DOM element to get the angle for
2018-05-16 00:53:27 +00:00
// toRad - Boolean that when true returns the value in radians rather than degrees
2016-04-24 20:43:20 +00:00
//
// Returns:
// Float with the angle in degrees or radians
2018-05-16 00:53:27 +00:00
svgedit . utilities . getRotationAngle = function ( elem , toRad ) {
2016-04-24 20:43:20 +00:00
var selected = elem || editorContext _ . getSelectedElements ( ) [ 0 ] ;
// find the rotation transform (if any) and set it
var tlist = svgedit . transformlist . getTransformList ( selected ) ;
2018-05-16 00:53:27 +00:00
return svgedit . utilities . getRotationAngleFromTransformList ( tlist , toRad ) ;
2016-04-24 20:43:20 +00:00
} ;
2013-02-14 15:19:46 +00:00
// Function getRefElem
// Get the reference element associated with the given attribute value
//
// Parameters:
// attrVal - The attribute value as a string
2018-05-16 00:53:27 +00:00
svgedit . utilities . getRefElem = function ( attrVal ) {
2013-02-14 15:19:46 +00:00
return svgedit . utilities . getElem ( svgedit . utilities . getUrlFromAttr ( attrVal ) . substr ( 1 ) ) ;
} ;
2011-02-04 08:02:46 +00:00
// Function: getElem
// Get a DOM element by ID within the SVG root element.
//
// Parameters:
// id - String with the element's new ID
if ( svgedit . browser . supportsSelectors ( ) ) {
2018-05-16 00:53:27 +00:00
svgedit . utilities . getElem = function ( id ) {
2011-02-04 08:02:46 +00:00
// querySelector lookup
2018-05-16 00:53:27 +00:00
return svgroot _ . querySelector ( '#' + id ) ;
2011-02-04 08:02:46 +00:00
} ;
} else if ( svgedit . browser . supportsXpath ( ) ) {
2018-05-16 00:53:27 +00:00
svgedit . utilities . getElem = function ( id ) {
2011-02-04 08:02:46 +00:00
// xpath lookup
return domdoc _ . evaluate (
2018-05-16 00:53:27 +00:00
'svg:svg[@id="svgroot"]//svg:*[@id="' + id + '"]' ,
2013-02-15 23:05:23 +00:00
domcontainer _ ,
2018-05-16 00:53:27 +00:00
function ( ) { return svgedit . NS . SVG ; } ,
2011-02-04 08:02:46 +00:00
9 ,
null ) . singleNodeValue ;
} ;
} else {
2018-05-16 00:53:27 +00:00
svgedit . utilities . getElem = function ( id ) {
2011-02-04 08:02:46 +00:00
// jQuery lookup: twice as slow as xpath in FF
return $ ( svgroot _ ) . find ( '[id=' + id + ']' ) [ 0 ] ;
} ;
}
// Function: assignAttributes
// Assigns multiple attributes to an element.
//
2018-05-16 00:53:27 +00:00
// Parameters:
2011-02-04 08:02:46 +00:00
// 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 svgedit.units.setUnitAttr
2018-05-16 00:53:27 +00:00
svgedit . utilities . assignAttributes = function ( node , attrs , suspendLength , unitCheck ) {
2014-01-31 02:13:37 +00:00
var i ;
for ( i in attrs ) {
2018-05-16 00:53:27 +00:00
var ns = ( i . substr ( 0 , 4 ) === 'xml:'
? NS . XML
: i . substr ( 0 , 6 ) === 'xlink:' ? NS . XLINK : null ) ;
2013-02-15 23:05:23 +00:00
2018-05-16 00:53:27 +00:00
if ( ns ) {
2011-02-04 08:02:46 +00:00
node . setAttributeNS ( ns , i , attrs [ i ] ) ;
2018-05-16 00:53:27 +00:00
} else if ( ! unitCheck ) {
2011-02-04 08:02:46 +00:00
node . setAttribute ( i , attrs [ i ] ) ;
} else {
svgedit . units . setUnitAttr ( node , i , attrs [ i ] ) ;
}
}
} ;
2011-02-10 04:10:03 +00:00
// Function: cleanupElement
// Remove unneeded (default) attributes, makes resulting SVG smaller
//
// Parameters:
// element - DOM element to clean up
2018-05-16 00:53:27 +00:00
svgedit . utilities . cleanupElement = function ( element ) {
2011-02-10 04:10:03 +00:00
var defaults = {
2018-05-16 00:53:27 +00:00
'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
2013-02-19 11:18:04 +00:00
} ;
2013-02-15 23:05:23 +00:00
2016-02-26 08:46:32 +00:00
if ( element . nodeName === 'ellipse' ) {
2016-02-25 08:03:20 +00:00
// Ellipse elements requires rx and ry attributes
delete defaults . rx ;
delete defaults . ry ;
}
2014-01-31 02:13:37 +00:00
var attr ;
for ( attr in defaults ) {
2011-02-10 04:10:03 +00:00
var val = defaults [ attr ] ;
2018-05-16 00:53:27 +00:00
if ( element . getAttribute ( attr ) == val ) {
2011-02-10 04:10:03 +00:00
element . removeAttribute ( attr ) ;
}
}
} ;
2013-02-17 04:58:04 +00:00
// Function: snapToGrid
// round value to for snapping
// NOTE: This function did not move to svgutils.js since it depends on curConfig.
2018-05-16 00:53:27 +00:00
svgedit . utilities . snapToGrid = function ( value ) {
2013-02-17 04:58:04 +00:00
var stepSize = editorContext _ . getSnappingStep ( ) ;
var unit = editorContext _ . getBaseUnit ( ) ;
2018-05-16 00:53:27 +00:00
if ( unit !== 'px' ) {
2013-02-17 04:58:04 +00:00
stepSize *= svgedit . units . getTypeMap ( ) [ unit ] ;
}
2018-05-16 00:53:27 +00:00
value = Math . round ( value / stepSize ) * stepSize ;
2013-02-17 04:58:04 +00:00
return value ;
} ;
2014-02-19 01:37:21 +00:00
svgedit . utilities . preg _quote = function ( str , delimiter ) {
2018-05-16 00:53:27 +00:00
// From: http://phpjs.org/functions
return String ( str ) . replace ( new RegExp ( '[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + ( delimiter || '' ) + '-]' , 'g' ) , '\\$&' ) ;
2014-02-19 01:37:21 +00:00
} ;
2014-05-22 10:21:29 +00:00
/ * *
* @ param { string } globalCheck A global which can be used to determine if the script is already loaded
* @ param { array } scripts An array of scripts to preload ( in order )
* @ param { function } cb The callback to execute upon load .
* /
svgedit . utilities . executeAfterLoads = function ( globalCheck , scripts , cb ) {
return function ( ) {
var args = arguments ;
function endCallback ( ) {
cb . apply ( null , args ) ;
}
if ( window [ globalCheck ] ) {
endCallback ( ) ;
2018-05-16 00:53:27 +00:00
} else {
2014-05-22 10:21:29 +00:00
scripts . reduceRight ( function ( oldFunc , script ) {
return function ( ) {
$ . getScript ( script , oldFunc ) ;
} ;
} , endCallback ) ( ) ;
}
} ;
} ;
svgedit . utilities . buildCanvgCallback = function ( callCanvg ) {
return svgedit . utilities . executeAfterLoads ( 'canvg' , [ 'canvg/rgbcolor.js' , 'canvg/canvg.js' ] , callCanvg ) ;
} ;
svgedit . utilities . buildJSPDFCallback = function ( callJSPDF ) {
return svgedit . utilities . executeAfterLoads ( 'RGBColor' , [ 'canvg/rgbcolor.js' ] , function ( ) {
var arr = [ ] ;
if ( ! RGBColor || RGBColor . ok === undef ) { // It's not our RGBColor, so we'll need to load it
arr . push ( 'canvg/rgbcolor.js' ) ;
}
svgedit . utilities . executeAfterLoads ( 'jsPDF' , arr . concat ( 'jspdf/underscore-min.js' , 'jspdf/jspdf.min.js' , 'jspdf/jspdf.plugin.svgToPdf.js' ) , callJSPDF ) ( ) ;
} ) ;
} ;
2016-05-04 13:38:29 +00:00
/ * *
* Prevents default browser click behaviour on the given element
* @ param img - The DOM element to prevent the click on
* /
2018-05-16 00:53:27 +00:00
svgedit . utilities . preventClickDefault = function ( img ) {
$ ( img ) . click ( function ( e ) { e . preventDefault ( ) ; } ) ;
2016-05-04 13:38:29 +00:00
} ;
/ * *
* Create a clone of an element , updating its ID and its children ' s IDs when needed
* @ param { Element } el - DOM element to clone
* @ param { function ( ) } getNextId - function the get the next unique ID .
* @ returns { Element }
* /
2018-05-16 00:53:27 +00:00
svgedit . utilities . copyElem = function ( el , getNextId ) {
2016-05-04 13:38:29 +00:00
// manually create a copy of the element
2018-05-16 00:53:27 +00:00
var newEl = document . createElementNS ( el . namespaceURI , el . nodeName ) ;
$ . each ( el . attributes , function ( i , attr ) {
2016-05-04 13:38:29 +00:00
if ( attr . localName != '-moz-math-font-style' ) {
2018-05-16 00:53:27 +00:00
newEl . setAttributeNS ( attr . namespaceURI , attr . nodeName , attr . value ) ;
2016-05-04 13:38:29 +00:00
}
} ) ;
// set the copied element's new id
2018-05-16 00:53:27 +00:00
newEl . removeAttribute ( 'id' ) ;
newEl . id = getNextId ( ) ;
2016-05-04 13:38:29 +00:00
// 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 ( svgedit . browser . isWebkit ( ) && el . nodeName == 'path' ) {
2018-05-16 00:53:27 +00:00
var fixedD = svgedit . utilities . convertPath ( el ) ;
newEl . setAttribute ( 'd' , fixedD ) ;
2016-05-04 13:38:29 +00:00
}
// now create copies of all children
2018-05-16 00:53:27 +00:00
$ . each ( el . childNodes , function ( i , child ) {
switch ( child . nodeType ) {
case 1 : // element node
newEl . appendChild ( svgedit . utilities . copyElem ( child , getNextId ) ) ;
break ;
case 3 : // text node
newEl . textContent = child . nodeValue ;
break ;
default :
break ;
2016-05-04 13:38:29 +00:00
}
} ) ;
if ( $ ( el ) . data ( 'gsvg' ) ) {
2018-05-16 00:53:27 +00:00
$ ( newEl ) . data ( 'gsvg' , newEl . firstChild ) ;
2016-05-04 13:38:29 +00:00
} else if ( $ ( el ) . data ( 'symbol' ) ) {
var ref = $ ( el ) . data ( 'symbol' ) ;
2018-05-16 00:53:27 +00:00
$ ( newEl ) . data ( 'ref' , ref ) . data ( 'symbol' , ref ) ;
} else if ( newEl . tagName == 'image' ) {
svgedit . utilities . preventClickDefault ( newEl ) ;
2016-05-04 13:38:29 +00:00
}
2018-01-18 12:37:49 +00:00
2018-05-16 00:53:27 +00:00
return newEl ;
2016-05-04 13:38:29 +00:00
} ;
/ * *
* TODO : refactor callers in convertPath to use getPathDFromSegments instead of this function .
* Legacy code refactored from svgcanvas . pathActions . convertPath
* @ param letter - path segment command
* @ param { Array . < Array . < number >> } points - x , y points .
* @ param { Array . < Array . < number >>= } morePoints - x , y points
* @ param { Array . < number >= } lastPoint - x , y point
* @ returns { string }
* /
2018-05-16 00:53:27 +00:00
function pathDSegment ( letter , points , morePoints , lastPoint ) {
$ . each ( points , function ( i , pnt ) {
2016-05-04 13:38:29 +00:00
points [ i ] = svgedit . units . shortFloat ( pnt ) ;
} ) ;
var segment = letter + points . join ( ' ' ) ;
2016-05-04 13:54:20 +00:00
if ( morePoints ) {
2016-05-04 13:38:29 +00:00
segment += ' ' + morePoints . join ( ' ' ) ;
}
2016-05-04 13:54:20 +00:00
if ( lastPoint ) {
2016-05-04 13:38:29 +00:00
segment += ' ' + svgedit . units . shortFloat ( lastPoint ) ;
}
return segment ;
}
// 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' ] ;
/ * *
* TODO : move to pathActions . js when migrating rest of pathActions out of svgcanvas . js
* Convert a path to one with only absolute or relative values
* @ param { Object } path - the path to convert
* @ param { boolean } toRel - true of convert to relative
* @ returns { string }
* /
2018-05-16 00:53:27 +00:00
svgedit . utilities . convertPath = function ( path , toRel ) {
2016-05-04 13:38:29 +00:00
var i ;
var segList = path . pathSegList ;
var len = segList . numberOfItems ;
var curx = 0 , cury = 0 ;
var d = '' ;
2018-05-16 00:53:27 +00:00
var lastM = null ;
2016-05-04 13:38:29 +00:00
for ( 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 ,
2018-05-16 00:53:27 +00:00
y = seg . y || 0 ,
x1 = seg . x1 || 0 ,
y1 = seg . y1 || 0 ,
x2 = seg . x2 || 0 ,
y2 = seg . y2 || 0 ;
2016-05-04 13:38:29 +00:00
var type = seg . pathSegType ;
2018-05-16 00:53:27 +00:00
var letter = pathMap [ type ] [ 'to' + ( toRel ? 'Lower' : 'Upper' ) + 'Case' ] ( ) ;
2016-05-04 13:38:29 +00:00
switch ( type ) {
2018-05-16 00:53:27 +00:00
case 1 : // z,Z closepath (Z/z)
d += 'z' ;
if ( lastM && ! toRel ) {
curx = lastM [ 0 ] ;
cury = lastM [ 1 ] ;
}
break ;
case 12 : // absolute horizontal line (H)
x -= curx ;
// Fallthrough
case 13 : // relative horizontal line (h)
if ( toRel ) {
curx += x ;
letter = 'l' ;
} else {
x += curx ;
curx = x ;
letter = 'L' ;
}
// Convert to "line" for easier editing
d += pathDSegment ( letter , [ [ x , cury ] ] ) ;
break ;
case 14 : // absolute vertical line (V)
y -= cury ;
// Fallthrough
case 15 : // relative vertical line (v)
if ( toRel ) {
cury += y ;
letter = 'l' ;
} else {
y += cury ;
cury = y ;
letter = 'L' ;
}
// Convert to "line" for easier editing
d += pathDSegment ( letter , [ [ curx , y ] ] ) ;
break ;
case 2 : // absolute move (M)
case 4 : // absolute line (L)
case 18 : // absolute smooth quad (T)
x -= curx ;
y -= cury ;
// Fallthrough
case 5 : // relative line (l)
case 3 : // relative move (m)
case 19 : // relative smooth quad (t)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ;
y += cury ;
curx = x ;
cury = y ;
}
if ( type === 2 || type === 3 ) { lastM = [ curx , cury ] ; }
d += pathDSegment ( letter , [ [ x , y ] ] ) ;
break ;
case 6 : // absolute cubic (C)
x -= curx ; x1 -= curx ; x2 -= curx ;
y -= cury ; y1 -= cury ; y2 -= cury ;
// Fallthrough
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 ;
}
d += pathDSegment ( letter , [ [ x1 , y1 ] , [ x2 , y2 ] , [ x , y ] ] ) ;
break ;
case 8 : // absolute quad (Q)
x -= curx ; x1 -= curx ;
y -= cury ; y1 -= cury ;
// Fallthrough
case 9 : // relative quad (q)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ; x1 += curx ;
y += cury ; y1 += cury ;
curx = x ;
cury = y ;
}
d += pathDSegment ( letter , [ [ x1 , y1 ] , [ x , y ] ] ) ;
break ;
case 10 : // absolute elliptical arc (A)
x -= curx ;
y -= cury ;
// Fallthrough
case 11 : // relative elliptical arc (a)
if ( toRel ) {
curx += x ;
cury += y ;
} else {
x += curx ;
y += cury ;
curx = x ;
cury = y ;
}
d += pathDSegment ( letter , [ [ 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 ;
// Fallthrough
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 ;
}
d += pathDSegment ( letter , [ [ x2 , y2 ] , [ x , y ] ] ) ;
break ;
2016-05-04 13:38:29 +00:00
} // switch on path segment type
} // for each segment
return d ;
} ;
2014-01-31 02:13:37 +00:00
} ( ) ) ;