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