1 (function(){ 2 3 /** 4 * @name fabric 5 * @namespace 6 */ 7 8 var fabric = this.fabric || (this.fabric = { }), 9 extend = fabric.util.object.extend, 10 capitalize = fabric.util.string.capitalize, 11 clone = fabric.util.object.clone; 12 13 var attributesMap = { 14 'cx': 'left', 15 'x': 'left', 16 'cy': 'top', 17 'y': 'top', 18 'r': 'radius', 19 'fill-opacity': 'opacity', 20 'fill-rule': 'fillRule', 21 'stroke-width': 'strokeWidth', 22 'transform': 'transformMatrix' 23 }; 24 25 /** 26 * Returns an object of attributes' name/value, given element and an array of attribute names; 27 * Parses parent "g" nodes recursively upwards. 28 * @static 29 * @memberOf fabric 30 * @method parseAttributes 31 * @param {DOMElement} element Element to parse 32 * @param {Array} attributes Array of attributes to parse 33 * @return {Object} object containing parsed attributes' names/values 34 */ 35 function parseAttributes(element, attributes) { 36 37 if (!element) { 38 return; 39 } 40 41 var value, 42 parsed, 43 parentAttributes = { }; 44 45 // if there's a parent container (`g` node), parse its attributes recursively upwards 46 if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) { 47 parentAttributes = fabric.parseAttributes(element.parentNode, attributes); 48 } 49 50 var ownAttributes = attributes.reduce(function(memo, attr) { 51 value = element.getAttribute(attr); 52 parsed = parseFloat(value); 53 if (value) { 54 // "normalize" attribute values 55 if ((attr === 'fill' || attr === 'stroke') && value === 'none') { 56 value = ''; 57 } 58 if (attr === 'fill-rule') { 59 value = (value === 'evenodd') ? 'destination-over' : value; 60 } 61 if (attr === 'transform') { 62 value = fabric.parseTransformAttribute(value); 63 } 64 // transform attribute names 65 if (attr in attributesMap) { 66 attr = attributesMap[attr]; 67 } 68 memo[attr] = isNaN(parsed) ? value : parsed; 69 } 70 return memo; 71 }, { }); 72 73 // add values parsed from style 74 // TODO (kangax): check the presedence of values from the style attribute 75 ownAttributes = extend(fabric.parseStyleAttribute(element), ownAttributes); 76 return extend(parentAttributes, ownAttributes); 77 }; 78 79 /** 80 * @static 81 * @function 82 * @memberOf fabric 83 * @method parseTransformAttribute 84 * @param attributeValue {String} string containing attribute value 85 * @return {Array} array of 6 elements representing transformation matrix 86 */ 87 fabric.parseTransformAttribute = (function() { 88 function rotateMatrix(matrix, args) { 89 var angle = args[0]; 90 91 matrix[0] = Math.cos(angle); 92 matrix[1] = Math.sin(angle); 93 matrix[2] = -Math.sin(angle); 94 matrix[3] = Math.cos(angle); 95 } 96 97 function scaleMatrix(matrix, args) { 98 var multiplierX = args[0], 99 multiplierY = (args.length === 2) ? args[1] : args[0]; 100 101 matrix[0] = multiplierX; 102 matrix[3] = multiplierY; 103 } 104 105 function skewXMatrix(matrix, args) { 106 matrix[2] = args[0]; 107 } 108 109 function skewYMatrix(matrix, args) { 110 matrix[1] = args[0]; 111 } 112 113 function translateMatrix(matrix, args) { 114 matrix[4] = args[0]; 115 if (args.length === 2) { 116 matrix[5] = args[1]; 117 } 118 } 119 120 // identity matrix 121 var iMatrix = [ 122 1, // a 123 0, // b 124 0, // c 125 1, // d 126 0, // e 127 0 // f 128 ], 129 130 // == begin transform regexp 131 number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)', 132 comma_wsp = '(?:\\s+,?\\s*|,\\s*)', 133 134 skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', 135 skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', 136 rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))', 137 scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))', 138 translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))', 139 140 matrix = '(?:(matrix)\\s*\\(\\s*' + 141 '(' + number + ')' + comma_wsp + 142 '(' + number + ')' + comma_wsp + 143 '(' + number + ')' + comma_wsp + 144 '(' + number + ')' + comma_wsp + 145 '(' + number + ')' + comma_wsp + 146 '(' + number + ')' + 147 '\\s*\\))', 148 149 transform = '(?:' + 150 matrix + '|' + 151 translate + '|' + 152 scale + '|' + 153 rotate + '|' + 154 skewX + '|' + 155 skewY + 156 ')', 157 158 transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')', 159 160 transform_list = '^\\s*(?:' + transforms + '?)\\s*$', 161 162 // http://www.w3.org/TR/SVG/coords.html#TransformAttribute 163 reTransformList = new RegExp(transform_list), 164 // == end transform regexp 165 166 reTransform = new RegExp(transform); 167 168 return function(attributeValue) { 169 170 // start with identity matrix 171 var matrix = iMatrix.concat(); 172 173 // return if no argument was given or 174 // an argument does not match transform attribute regexp 175 if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { 176 return matrix; 177 } 178 179 attributeValue.replace(reTransform, function(match) { 180 181 var m = new RegExp(transform).exec(match).filter(function (match) { 182 return (match !== '' && match != null); 183 }), 184 operation = m[1], 185 args = m.slice(2).map(parseFloat); 186 187 switch(operation) { 188 case 'translate': 189 translateMatrix(matrix, args); 190 break; 191 case 'rotate': 192 rotateMatrix(matrix, args); 193 break; 194 case 'scale': 195 scaleMatrix(matrix, args); 196 break; 197 case 'skewX': 198 skewXMatrix(matrix, args); 199 break; 200 case 'skewY': 201 skewYMatrix(matrix, args); 202 break; 203 case 'matrix': 204 matrix = args; 205 break; 206 } 207 }) 208 return matrix; 209 } 210 })(); 211 212 /** 213 * @static 214 * @memberOf fabric 215 * @method parsePointsAttribute 216 * @param points {String} points attribute string 217 * @return {Array} array of points 218 */ 219 function parsePointsAttribute(points) { 220 221 // points attribute is required and must not be empty 222 if (!points) return null; 223 224 points = points.trim(); 225 var asPairs = points.indexOf(',') > -1; 226 227 points = points.split(/\s+/); 228 var parsedPoints = [ ]; 229 230 // points could look like "10,20 30,40" or "10 20 30 40" 231 if (asPairs) { 232 for (var i = 0, len = points.length; i < len; i++) { 233 var pair = points[i].split(','); 234 parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) }); 235 } 236 } 237 else { 238 for (var i = 0, len = points.length; i < len; i+=2) { 239 parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) }); 240 } 241 } 242 243 // odd number of points is an error 244 if (parsedPoints.length % 2 !== 0) { 245 // return null; 246 } 247 248 return parsedPoints; 249 }; 250 251 /** 252 * @static 253 * @memberOf fabric 254 * @method parseStyleAttribute 255 * @param {SVGElement} element Element to parse 256 * @return {Object} Objects with values parsed from style attribute of an element 257 */ 258 function parseStyleAttribute(element) { 259 var oStyle = { }, 260 style = element.getAttribute('style'); 261 if (style) { 262 if (typeof style == 'string') { 263 style = style.split(';'); 264 style.pop(); 265 oStyle = style.reduce(function(memo, current) { 266 var attr = current.split(':'), 267 key = attr[0].trim(), 268 value = attr[1].trim(); 269 memo[key] = value; 270 return memo; 271 }, { }); 272 } 273 else { 274 for (var prop in style) { 275 if (typeof style[prop] !== 'undefined') { 276 oStyle[prop] = style[prop]; 277 } 278 } 279 } 280 } 281 return oStyle; 282 }; 283 284 /** 285 * @static 286 * @memberOf fabric 287 * @method parseElements 288 * @param {Array} elements Array of elements to parse 289 * @param {Object} options Options object 290 * @return {Array} Array of corresponding instances (transformed from SVG elements) 291 */ 292 function parseElements(elements, options) { 293 // transform svg elements to fabric.Path elements 294 var _elements = elements.map(function(el) { 295 var klass = fabric[capitalize(el.tagName)]; 296 if (klass && klass.fromElement) { 297 try { 298 return klass.fromElement(el, options); 299 } 300 catch(e) { 301 fabric.log(e.message || e); 302 } 303 } 304 }); 305 _elements = _elements.filter(function(el) { 306 return el != null; 307 }); 308 return _elements; 309 }; 310 311 /** 312 * @static 313 * @function 314 * @memberOf fabric 315 * @method parseSVGDocument 316 * @param {SVGDocument} doc SVG document to parse 317 * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). 318 */ 319 fabric.parseSVGDocument = (function() { 320 321 var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line)$/; 322 323 // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 324 // \d doesn't quite cut it (as we need to match an actual float number) 325 326 // matches, e.g.: +14.56e-12, etc. 327 var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)'; 328 329 var reViewBoxAttrValue = new RegExp( 330 '^' + 331 '\\s*(' + reNum + '+)\\s*,?' + 332 '\\s*(' + reNum + '+)\\s*,?' + 333 '\\s*(' + reNum + '+)\\s*,?' + 334 '\\s*(' + reNum + '+)\\s*' + 335 '$' 336 ); 337 338 function hasParentWithNodeName(element, parentNodeName) { 339 while (element && (element = element.parentNode)) { 340 if (element.nodeName === parentNodeName) { 341 return true; 342 } 343 } 344 return false; 345 } 346 347 return function(doc, callback) { 348 if (!doc) return; 349 var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); 350 351 var elements = descendants.filter(function(el) { 352 return reAllowedSVGTagNames.test(el.tagName) && 353 !hasParentWithNodeName(el, 'pattern'); 354 }); 355 356 if (!elements || (elements && !elements.length)) return; 357 358 var viewBoxAttr = doc.getAttribute('viewBox'), 359 widthAttr = doc.getAttribute('width'), 360 heightAttr = doc.getAttribute('height'), 361 width = null, 362 height = null, 363 minX, 364 minY; 365 366 if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { 367 minX = parseInt(viewBoxAttr[1], 10); 368 minY = parseInt(viewBoxAttr[2], 10); 369 width = parseInt(viewBoxAttr[3], 10); 370 height = parseInt(viewBoxAttr[4], 10); 371 } 372 373 // values of width/height attributes overwrite those extracted from viewbox attribute 374 width = widthAttr ? parseFloat(widthAttr) : width; 375 height = heightAttr ? parseFloat(heightAttr) : height; 376 377 var options = { 378 width: width, 379 height: height 380 }; 381 382 var elements = fabric.parseElements(elements, clone(options)); 383 if (!elements || (elements && !elements.length)) return; 384 385 if (callback) { 386 callback(elements, options); 387 } 388 }; 389 })(); 390 391 extend(fabric, { 392 parseAttributes: parseAttributes, 393 parseElements: parseElements, 394 parseStyleAttribute: parseStyleAttribute, 395 parsePointsAttribute: parsePointsAttribute 396 }); 397 398 })();