fabric.js/src/parser.js

991 lines
31 KiB
JavaScript
Raw Normal View History

(function(global) {
'use strict';
2010-10-19 20:27:24 +00:00
/**
* @name fabric
* @namespace
*/
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend,
clone = fabric.util.object.clone,
toFixed = fabric.util.toFixed,
parseUnit = fabric.util.parseUnit,
multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
2015-07-15 15:05:34 +00:00
reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i,
reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i,
reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata|clipPath|mask)$/i,
2015-07-15 15:05:34 +00:00
reAllowedParents = /^(symbol|g|a|svg)$/i,
attributesMap = {
cx: 'left',
x: 'left',
r: 'radius',
cy: 'top',
y: 'top',
display: 'visible',
visibility: 'visible',
transform: 'transformMatrix',
'fill-opacity': 'fillOpacity',
'fill-rule': 'fillRule',
'font-family': 'fontFamily',
'font-size': 'fontSize',
'font-style': 'fontStyle',
'font-weight': 'fontWeight',
'stroke-dasharray': 'strokeDashArray',
'stroke-linecap': 'strokeLineCap',
'stroke-linejoin': 'strokeLineJoin',
'stroke-miterlimit': 'strokeMiterLimit',
'stroke-opacity': 'strokeOpacity',
'stroke-width': 'strokeWidth',
2014-04-18 20:07:49 +00:00
'text-decoration': 'textDecoration',
'text-anchor': 'originX',
opacity: 'opacity'
},
colorAttributes = {
stroke: 'strokeOpacity',
fill: 'fillOpacity'
};
2014-09-20 23:16:25 +00:00
fabric.cssRules = { };
fabric.gradientDefs = { };
function normalizeAttr(attr) {
// transform attribute names
if (attr in attributesMap) {
return attributesMap[attr];
}
return attr;
}
2014-10-21 20:50:34 +00:00
function normalizeValue(attr, value, parentAttributes, fontSize) {
2014-07-18 11:11:18 +00:00
var isArray = Object.prototype.toString.call(value) === '[object Array]',
parsed;
2013-05-01 06:58:52 +00:00
if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
value = '';
}
else if (attr === 'strokeDashArray') {
2016-11-13 20:00:10 +00:00
if (value === 'none') {
value = null;
}
else {
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
return parseFloat(n);
});
}
2013-05-01 06:58:52 +00:00
}
else if (attr === 'transformMatrix') {
2013-05-01 06:58:52 +00:00
if (parentAttributes && parentAttributes.transformMatrix) {
value = multiplyTransformMatrices(
2013-05-01 06:58:52 +00:00
parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
}
else {
value = fabric.parseTransformAttribute(value);
}
2013-05-01 06:58:52 +00:00
}
else if (attr === 'visible') {
value = (value === 'none' || value === 'hidden') ? false : true;
// display=none on parent element always takes precedence over child element
2014-06-21 12:22:10 +00:00
if (parentAttributes && parentAttributes.visible === false) {
value = false;
}
}
else if (attr === 'opacity') {
value = parseFloat(value);
if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
value *= parentAttributes.opacity;
}
}
2014-04-18 20:07:49 +00:00
else if (attr === 'originX' /* text-anchor */) {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
2014-07-18 11:11:18 +00:00
}
else {
2014-10-21 20:50:34 +00:00
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
2014-04-18 20:07:49 +00:00
}
return (!isArray && isNaN(parsed) ? value : parsed);
2013-05-01 06:58:52 +00:00
}
/**
* @private
* @param {Object} attributes Array of attributes to parse
*/
function _setStrokeFillOpacity(attributes) {
for (var attr in colorAttributes) {
if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
continue;
}
if (typeof attributes[attr] === 'undefined') {
if (!fabric.Object.prototype[attr]) {
continue;
}
attributes[attr] = fabric.Object.prototype[attr];
}
if (attributes[attr].indexOf('url(') === 0) {
continue;
}
var color = new fabric.Color(attributes[attr]);
attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
}
return attributes;
}
/**
* @private
*/
function _getMultipleNodes(doc, nodeNames) {
var nodeName, nodeArray = [], nodeList;
for (var i = 0; i < nodeNames.length; i++) {
nodeName = nodeNames[i];
nodeList = doc.getElementsByTagName(nodeName);
nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
}
return nodeArray;
}
2010-06-09 22:34:55 +00:00
/**
2010-10-19 20:27:24 +00:00
* Parses "transform" attribute, returning an array of values
2010-06-09 22:34:55 +00:00
* @static
* @function
* @memberOf fabric
2013-11-21 17:00:29 +00:00
* @param {String} attributeValue String containing attribute value
* @return {Array} Array of 6 elements representing transformation matrix
2010-06-09 22:34:55 +00:00
*/
fabric.parseTransformAttribute = (function() {
2010-06-09 22:34:55 +00:00
function rotateMatrix(matrix, args) {
var cos = Math.cos(args[0]), sin = Math.sin(args[0]),
x = 0, y = 0;
if (args.length === 3) {
x = args[1];
y = args[2];
}
matrix[0] = cos;
matrix[1] = sin;
matrix[2] = -sin;
matrix[3] = cos;
matrix[4] = x - (cos * x - sin * y);
matrix[5] = y - (sin * x + cos * y);
2010-06-09 22:34:55 +00:00
}
2010-06-09 22:34:55 +00:00
function scaleMatrix(matrix, args) {
var multiplierX = args[0],
multiplierY = (args.length === 2) ? args[1] : args[0];
matrix[0] = multiplierX;
matrix[3] = multiplierY;
}
function skewMatrix(matrix, args, pos) {
matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0]));
2010-06-09 22:34:55 +00:00
}
2010-06-09 22:34:55 +00:00
function translateMatrix(matrix, args) {
matrix[4] = args[0];
if (args.length === 2) {
matrix[5] = args[1];
}
}
2010-06-09 22:34:55 +00:00
// identity matrix
var iMatrix = [
1, // a
0, // b
0, // c
1, // d
0, // e
0 // f
],
2010-06-09 22:34:55 +00:00
// == begin transform regexp
2014-10-21 20:50:34 +00:00
number = fabric.reNum,
2013-11-05 10:59:48 +00:00
commaWsp = '(?:\\s+,?\\s*|,\\s*)',
2010-06-09 22:34:55 +00:00
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
2013-11-05 10:59:48 +00:00
2010-06-09 22:34:55 +00:00
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
2013-11-05 10:59:48 +00:00
rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + ')' +
commaWsp + '(' + number + '))?\\s*\\))',
2013-11-05 10:59:48 +00:00
scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
2013-11-05 10:59:48 +00:00
translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
matrix = '(?:(matrix)\\s*\\(\\s*' +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' +
2010-06-09 22:34:55 +00:00
'\\s*\\))',
2010-06-09 22:34:55 +00:00
transform = '(?:' +
matrix + '|' +
translate + '|' +
scale + '|' +
rotate + '|' +
skewX + '|' +
skewY +
2010-06-09 22:34:55 +00:00
')',
transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
transformList = '^\\s*(?:' + transforms + '?)\\s*$',
2010-06-09 22:34:55 +00:00
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
reTransformList = new RegExp(transformList),
2010-06-09 22:34:55 +00:00
// == end transform regexp
reTransform = new RegExp(transform, 'g');
2010-06-09 22:34:55 +00:00
return function(attributeValue) {
2010-06-09 22:34:55 +00:00
// start with identity matrix
var matrix = iMatrix.concat(),
matrices = [];
// return if no argument was given or
2010-06-09 22:34:55 +00:00
// an argument does not match transform attribute regexp
if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
return matrix;
}
2010-06-09 22:34:55 +00:00
attributeValue.replace(reTransform, function(match) {
var m = new RegExp(transform).exec(match).filter(function (match) {
// match !== '' && match != null
return (!!match);
2010-06-09 22:34:55 +00:00
}),
operation = m[1],
args = m.slice(2).map(parseFloat);
switch (operation) {
2010-06-09 22:34:55 +00:00
case 'translate':
translateMatrix(matrix, args);
break;
case 'rotate':
args[0] = fabric.util.degreesToRadians(args[0]);
2010-06-09 22:34:55 +00:00
rotateMatrix(matrix, args);
break;
case 'scale':
scaleMatrix(matrix, args);
break;
case 'skewX':
skewMatrix(matrix, args, 2);
2010-06-09 22:34:55 +00:00
break;
case 'skewY':
skewMatrix(matrix, args, 1);
2010-06-09 22:34:55 +00:00
break;
case 'matrix':
matrix = args;
break;
}
// snapshot current matrix into matrices array
matrices.push(matrix.concat());
// reset
matrix = iMatrix.concat();
});
var combinedMatrix = matrices[0];
while (matrices.length > 1) {
matrices.shift();
combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
}
return combinedMatrix;
};
2010-06-09 22:34:55 +00:00
})();
2013-11-04 15:41:26 +00:00
/**
* @private
*/
function parseStyleString(style, oStyle) {
var attr, value;
style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
2013-11-04 15:41:26 +00:00
var pair = chunk.split(':');
attr = pair[0].trim().toLowerCase();
value = pair[1].trim();
2013-11-04 15:41:26 +00:00
2014-10-21 20:50:34 +00:00
oStyle[attr] = value;
2013-11-04 15:41:26 +00:00
});
}
2013-11-04 15:41:26 +00:00
/**
* @private
*/
function parseStyleObject(style, oStyle) {
var attr, value;
for (var prop in style) {
if (typeof style[prop] === 'undefined') {
continue;
}
2013-11-04 15:41:26 +00:00
attr = prop.toLowerCase();
value = style[prop];
2013-11-04 15:41:26 +00:00
2014-10-21 20:50:34 +00:00
oStyle[attr] = value;
2013-11-04 15:41:26 +00:00
}
}
/**
* @private
*/
2014-09-20 23:16:25 +00:00
function getGlobalStylesForElement(element, svgUid) {
2014-07-26 17:19:22 +00:00
var styles = { };
2014-09-20 23:16:25 +00:00
for (var rule in fabric.cssRules[svgUid]) {
2014-07-26 17:19:22 +00:00
if (elementMatchesRule(element, rule.split(' '))) {
2014-09-20 23:16:25 +00:00
for (var property in fabric.cssRules[svgUid][rule]) {
styles[property] = fabric.cssRules[svgUid][rule][property];
}
}
}
return styles;
}
2014-07-26 17:19:22 +00:00
/**
* @private
*/
function elementMatchesRule(element, selectors) {
var firstMatching, parentMatching = true;
//start from rightmost selector.
firstMatching = selectorMatches(element, selectors.pop());
if (firstMatching && selectors.length) {
parentMatching = doesSomeParentMatch(element, selectors);
}
return firstMatching && parentMatching && (selectors.length === 0);
}
function doesSomeParentMatch(element, selectors) {
var selector, parentMatching = true;
while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
if (parentMatching) {
selector = selectors.pop();
}
element = element.parentNode;
parentMatching = selectorMatches(element, selector);
}
return selectors.length === 0;
}
2015-04-20 00:51:32 +00:00
2014-07-26 17:19:22 +00:00
/**
* @private
*/
function selectorMatches(element, selector) {
var nodeName = element.nodeName,
classNames = element.getAttribute('class'),
id = element.getAttribute('id'), matcher;
// i check if a selector matches slicing away part from it.
// if i get empty string i should match
matcher = new RegExp('^' + nodeName, 'i');
selector = selector.replace(matcher, '');
if (id && selector.length) {
matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
selector = selector.replace(matcher, '');
}
if (classNames && selector.length) {
classNames = classNames.split(' ');
for (var i = classNames.length; i--;) {
matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
selector = selector.replace(matcher, '');
}
}
return selector.length === 0;
}
2015-05-13 06:27:23 +00:00
/**
* @private
* to support IE8 missing getElementById on SVGdocument
*/
function elementById(doc, id) {
var el;
doc.getElementById && (el = doc.getElementById(id));
2015-05-11 06:48:12 +00:00
if (el) {
return el;
}
var node, i, nodelist = doc.getElementsByTagName('*');
2015-05-11 06:48:12 +00:00
for (i = 0; i < nodelist.length; i++) {
node = nodelist[i];
if (id === node.getAttribute('id')) {
2015-05-13 06:27:23 +00:00
return node;
}
2015-05-11 06:48:12 +00:00
}
}
/**
* @private
*/
function parseUseDirectives(doc) {
var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
2015-05-11 06:48:12 +00:00
while (nodelist.length && i < nodelist.length) {
var el = nodelist[i],
2014-07-17 14:18:57 +00:00
xlink = el.getAttribute('xlink:href').substr(1),
x = el.getAttribute('x') || 0,
y = el.getAttribute('y') || 0,
2015-05-13 06:27:23 +00:00
el2 = elementById(doc, xlink).cloneNode(true),
2014-10-17 15:57:10 +00:00
currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
2015-07-15 15:05:34 +00:00
parentNode, oldLength = nodelist.length, attr, j, attrs, l;
applyViewboxTransform(el2);
if (/^svg$/i.test(el2.nodeName)) {
var el3 = el2.ownerDocument.createElement('g');
for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) {
attr = attrs.item(j);
el3.setAttribute(attr.nodeName, attr.nodeValue);
}
// el2.firstChild != null
while (el2.firstChild) {
2015-07-15 15:05:34 +00:00
el3.appendChild(el2.firstChild);
}
el2 = el3;
}
2014-07-17 14:18:57 +00:00
2015-07-15 15:05:34 +00:00
for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
attr = attrs.item(j);
if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
continue;
}
2014-07-17 14:18:57 +00:00
if (attr.nodeName === 'transform') {
2014-10-21 20:50:34 +00:00
currentTrans = attr.nodeValue + ' ' + currentTrans;
2014-07-17 14:18:57 +00:00
}
else {
el2.setAttribute(attr.nodeName, attr.nodeValue);
}
}
2014-07-17 14:18:57 +00:00
el2.setAttribute('transform', currentTrans);
2014-10-17 15:57:10 +00:00
el2.setAttribute('instantiated_by_use', '1');
el2.removeAttribute('id');
2014-07-17 14:18:57 +00:00
parentNode = el.parentNode;
parentNode.replaceChild(el2, el);
2015-05-13 06:27:23 +00:00
// some browsers do not shorten nodelist after replaceChild (IE8)
2015-05-11 06:48:12 +00:00
if (nodelist.length === oldLength) {
i++;
}
}
}
2015-02-07 13:14:32 +00:00
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// matches, e.g.: +14.56e-12, etc.
var reViewBoxAttrValue = new RegExp(
'^' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*' +
'$'
);
2014-07-18 14:37:55 +00:00
/**
2014-10-17 15:57:10 +00:00
* Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
2014-07-18 14:37:55 +00:00
*/
2015-07-15 15:05:34 +00:00
function applyViewboxTransform(element) {
2014-10-17 15:57:10 +00:00
2015-02-07 13:14:32 +00:00
var viewBoxAttr = element.getAttribute('viewBox'),
scaleX = 1,
scaleY = 1,
minX = 0,
minY = 0,
2015-07-15 15:05:34 +00:00
viewBoxWidth, viewBoxHeight, matrix, el,
widthAttr = element.getAttribute('width'),
heightAttr = element.getAttribute('height'),
2015-08-09 11:38:51 +00:00
x = element.getAttribute('x') || 0,
y = element.getAttribute('y') || 0,
preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName)
2015-07-15 15:05:34 +00:00
|| !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
toBeParsed = missingViewBox && missingDimAttr,
2015-08-09 11:38:51 +00:00
parsedDim = { }, translateMatrix = '';
2015-07-15 15:05:34 +00:00
parsedDim.width = 0;
parsedDim.height = 0;
parsedDim.toBeParsed = toBeParsed;
if (toBeParsed) {
return parsedDim;
}
2015-07-17 09:36:41 +00:00
2015-07-15 15:05:34 +00:00
if (missingViewBox) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
return parsedDim;
}
minX = -parseFloat(viewBoxAttr[1]);
minY = -parseFloat(viewBoxAttr[2]);
viewBoxWidth = parseFloat(viewBoxAttr[3]);
2015-07-15 15:05:34 +00:00
viewBoxHeight = parseFloat(viewBoxAttr[4]);
if (!missingDimAttr) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
scaleX = parsedDim.width / viewBoxWidth;
scaleY = parsedDim.height / viewBoxHeight;
2014-10-17 15:57:10 +00:00
}
else {
2015-07-15 15:05:34 +00:00
parsedDim.width = viewBoxWidth;
parsedDim.height = viewBoxHeight;
2015-07-17 09:36:41 +00:00
}
2014-10-17 15:57:10 +00:00
// default is to preserve aspect ratio
2015-08-09 11:38:51 +00:00
preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
if (preserveAspectRatio.alignX !== 'none') {
//translate all container for the effect of Mid, Min, Max
scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
}
2014-10-17 15:57:10 +00:00
2015-08-09 11:38:51 +00:00
if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
2015-07-15 15:05:34 +00:00
return parsedDim;
2014-10-17 15:57:10 +00:00
}
2015-07-15 15:05:34 +00:00
2015-08-09 11:38:51 +00:00
if (x || y) {
translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
}
matrix = translateMatrix + ' matrix(' + scaleX +
2014-10-17 15:57:10 +00:00
' 0' +
' 0 ' +
scaleY + ' ' +
(minX * scaleX) + ' ' +
(minY * scaleY) + ') ';
2014-10-17 15:57:10 +00:00
if (element.nodeName === 'svg') {
2014-10-17 15:57:10 +00:00
el = element.ownerDocument.createElement('g');
// element.firstChild != null
while (element.firstChild) {
2014-10-17 15:57:10 +00:00
el.appendChild(element.firstChild);
}
element.appendChild(el);
}
else {
el = element;
matrix = el.getAttribute('transform') + matrix;
2014-10-17 15:57:10 +00:00
}
el.setAttribute('transform', matrix);
2015-07-15 15:05:34 +00:00
return parsedDim;
2014-07-18 14:37:55 +00:00
}
function hasAncestorWithNodeName(element, nodeName) {
while (element && (element = element.parentNode)) {
if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', ''))
&& !element.getAttribute('instantiated_by_use')) {
return true;
}
}
return false;
}
2010-06-09 22:34:55 +00:00
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
2010-06-09 22:34:55 +00:00
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @param {Function} callback Callback to call when parsing is finished;
* It's being passed an array of elements (parsed from a document).
2012-08-20 00:36:37 +00:00
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
2017-03-25 17:44:09 +00:00
* @param {Object} [parsingOptions] options for parsing document
* @param {String} [parsingOptions.crossOrigin] crossOrigin settings
2010-06-09 22:34:55 +00:00
*/
2017-03-25 17:44:09 +00:00
fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
if (!doc) {
return;
2010-06-09 22:34:55 +00:00
}
parseUseDirectives(doc);
2015-03-31 02:09:58 +00:00
var svgUid = fabric.Object.__uid++,
options = applyViewboxTransform(doc),
descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
2017-03-25 17:44:09 +00:00
options.crossOrigin = parsingOptions.crossOrigin;
options.svgUid = svgUid;
if (descendants.length === 0 && fabric.isLikelyNode) {
// we're likely in node, where "o3-xml" library fails to gEBTN("*")
// https://github.com/ajaxorg/node-o3-xml/issues/21
descendants = doc.selectNodes('//*[name(.)!="svg"]');
var arr = [];
for (var i = 0, len = descendants.length; i < len; i++) {
arr[i] = descendants[i];
}
descendants = arr;
2014-06-24 12:12:17 +00:00
}
var elements = descendants.filter(function(el) {
applyViewboxTransform(el);
return reAllowedSVGTagNames.test(el.nodeName.replace('svg:', '')) &&
!hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement
2014-06-24 12:12:17 +00:00
});
if (!elements || (elements && !elements.length)) {
callback && callback([], {});
return;
}
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
if (callback) {
callback(instances, options);
}
2017-03-25 17:44:09 +00:00
}, clone(options), reviver, parsingOptions);
};
2015-02-07 13:14:32 +00:00
var reFontDeclaration = new RegExp(
'(normal|italic)?\\s*(normal|small-caps)?\\s*' +
'(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
fabric.reNum +
'(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
2013-11-21 17:00:29 +00:00
extend(fabric, {
2014-10-21 20:50:34 +00:00
/**
* Parses a short font declaration, building adding its properties to a style object
* @static
* @function
* @memberOf fabric
* @param {String} value font declaration
* @param {Object} oStyle definition
*/
parseFontDeclaration: function(value, oStyle) {
2015-02-07 13:14:32 +00:00
var match = value.match(reFontDeclaration);
2014-10-21 20:50:34 +00:00
if (!match) {
return;
}
var fontStyle = match[1],
// font variant is not used
// fontVariant = match[2],
fontWeight = match[3],
fontSize = match[4],
lineHeight = match[5],
fontFamily = match[6];
if (fontStyle) {
oStyle.fontStyle = fontStyle;
}
if (fontWeight) {
oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
}
if (fontSize) {
oStyle.fontSize = parseUnit(fontSize);
}
if (fontFamily) {
oStyle.fontFamily = fontFamily;
}
if (lineHeight) {
oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
}
},
2013-11-21 17:00:29 +00:00
/**
* Parses an SVG document, returning all of the gradient declarations found in it
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
*/
getGradientDefs: function(doc) {
var tagArray = [
'linearGradient',
'radialGradient',
'svg:linearGradient',
'svg:radialGradient'],
elList = _getMultipleNodes(doc, tagArray),
el, j = 0, id, xlink,
gradientDefs = { }, idsToXlinkMap = { };
2013-11-21 17:00:29 +00:00
j = elList.length;
while (j--) {
el = elList[j];
xlink = el.getAttribute('xlink:href');
id = el.getAttribute('id');
if (xlink) {
idsToXlinkMap[id] = xlink.substr(1);
}
gradientDefs[id] = el;
2013-11-21 17:00:29 +00:00
}
2014-08-16 14:26:08 +00:00
for (id in idsToXlinkMap) {
var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
el = gradientDefs[id];
while (el2.firstChild) {
el.appendChild(el2.firstChild);
}
}
2013-11-21 17:00:29 +00:00
return gradientDefs;
},
/**
* Returns an object of attributes' name/value, given element and an array of attribute names;
* Parses parent "g" nodes recursively upwards.
* @static
* @memberOf fabric
* @param {DOMElement} element Element to parse
* @param {Array} attributes Array of attributes to parse
* @return {Object} object containing parsed attributes' names/values
*/
2014-09-20 23:16:25 +00:00
parseAttributes: function(element, attributes, svgUid) {
2013-11-21 17:00:29 +00:00
if (!element) {
return;
}
var value,
2014-10-21 20:50:34 +00:00
parentAttributes = { },
fontSize;
2014-09-20 23:16:25 +00:00
if (typeof svgUid === 'undefined') {
svgUid = element.getAttribute('svgUid');
}
// if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
2015-07-15 15:05:34 +00:00
if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) {
2014-09-20 23:16:25 +00:00
parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
2013-11-21 17:00:29 +00:00
}
2014-10-21 20:50:34 +00:00
fontSize = (parentAttributes && parentAttributes.fontSize ) ||
element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
2013-11-21 17:00:29 +00:00
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
if (value) { // eslint-disable-line
2013-11-21 17:00:29 +00:00
memo[attr] = value;
}
return memo;
}, { });
// add values parsed from style, which take precedence over attributes
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
ownAttributes = extend(ownAttributes,
2014-09-20 23:16:25 +00:00
extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
var normalizedAttr, normalizedValue, normalizedStyle = {};
for (var attr in ownAttributes) {
normalizedAttr = normalizeAttr(attr);
normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
normalizedStyle[normalizedAttr] = normalizedValue;
}
if (normalizedStyle && normalizedStyle.font) {
fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
2014-10-21 20:50:34 +00:00
}
var mergedAttrs = extend(parentAttributes, normalizedStyle);
return reAllowedParents.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
2013-11-21 17:00:29 +00:00
},
/**
* Transforms an array of svg elements to corresponding fabric.* instances
* @static
* @memberOf fabric
* @param {Array} elements Array of elements to parse
* @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
* @param {Object} [options] Options object
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
2017-03-25 17:44:09 +00:00
parseElements: function(elements, callback, options, reviver, parsingOptions) {
new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse();
2013-11-21 17:00:29 +00:00
},
/**
* Parses "style" attribute, retuning an object with values
* @static
* @memberOf fabric
* @param {SVGElement} element Element to parse
* @return {Object} Objects with values parsed from style attribute of an element
*/
parseStyleAttribute: function(element) {
var oStyle = { },
style = element.getAttribute('style');
if (!style) {
return oStyle;
}
2013-11-21 17:00:29 +00:00
if (typeof style === 'string') {
parseStyleString(style, oStyle);
}
else {
parseStyleObject(style, oStyle);
}
return oStyle;
},
/**
* Parses "points" attribute, returning an array of values
* @static
* @memberOf fabric
2014-07-17 14:18:57 +00:00
* @param {String} points points attribute string
2013-11-21 17:00:29 +00:00
* @return {Array} array of points
*/
parsePointsAttribute: function(points) {
// points attribute is required and must not be empty
if (!points) {
return null;
}
2013-11-21 17:00:29 +00:00
// replace commas with whitespace and remove bookending whitespace
points = points.replace(/,/g, ' ').trim();
2013-11-21 17:00:29 +00:00
points = points.split(/\s+/);
var parsedPoints = [], i, len;
2013-11-21 17:00:29 +00:00
i = 0;
len = points.length;
for (; i < len; i += 2) {
parsedPoints.push({
x: parseFloat(points[i]),
y: parseFloat(points[i + 1])
});
2013-11-21 17:00:29 +00:00
}
// odd number of points is an error
// if (parsedPoints.length % 2 !== 0) {
2015-03-30 23:22:29 +00:00
// return null;
// }
2013-11-21 17:00:29 +00:00
return parsedPoints;
},
/**
* Returns CSS rules for a given SVG document
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @return {Object} CSS rules of this document
*/
getCSSRules: function(doc) {
var styles = doc.getElementsByTagName('style'),
2014-07-26 17:19:22 +00:00
allRules = { }, rules;
2013-11-21 17:00:29 +00:00
// very crude parsing of style contents
for (var i = 0, len = styles.length; i < len; i++) {
// IE9 doesn't support textContent, but provides text instead.
var styleContents = styles[i].textContent || styles[i].text;
2013-11-21 17:00:29 +00:00
// remove comments
styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
if (styleContents.trim() === '') {
continue;
}
2013-11-21 17:00:29 +00:00
rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
rules = rules.map(function(rule) { return rule.trim(); });
rules.forEach(function(rule) {
2014-07-26 17:19:22 +00:00
var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
ruleObj = { }, declaration = match[2].trim(),
propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
2013-11-21 17:00:29 +00:00
for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
var pair = propertyValuePairs[i].split(/\s*:\s*/),
property = pair[0],
value = pair[1];
2014-07-26 17:19:22 +00:00
ruleObj[property] = value;
2013-11-21 17:00:29 +00:00
}
2014-07-26 17:19:22 +00:00
rule = match[1];
rule.split(',').forEach(function(_rule) {
2014-10-21 20:50:34 +00:00
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
}
if (allRules[_rule]) {
fabric.util.object.extend(allRules[_rule], ruleObj);
}
else {
allRules[_rule] = fabric.util.object.clone(ruleObj);
}
2014-07-26 17:19:22 +00:00
});
2013-11-21 17:00:29 +00:00
});
}
return allRules;
},
/**
* Takes url corresponding to an SVG document, and parses it into a set of fabric objects.
* Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
2015-05-22 06:08:20 +00:00
* @memberOf fabric
2013-11-21 17:00:29 +00:00
* @param {String} url
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
2017-03-25 17:44:09 +00:00
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
2013-11-21 17:00:29 +00:00
*/
2017-03-25 17:44:09 +00:00
loadSVGFromURL: function(url, callback, reviver, options) {
2013-11-21 17:00:29 +00:00
url = url.replace(/^\n\s*/, '').trim();
new fabric.util.request(url, {
method: 'get',
onComplete: onComplete
2013-11-21 17:00:29 +00:00
});
function onComplete(r) {
var xml = r.responseXML;
if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
2013-11-21 17:00:29 +00:00
xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = 'false';
//IE chokes on DOCTYPE
xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
2013-11-21 17:00:29 +00:00
}
if (!xml || !xml.documentElement) {
callback && callback(null);
}
2013-11-21 17:00:29 +00:00
2017-03-25 17:44:09 +00:00
fabric.parseSVGDocument(xml.documentElement, function (results, _options) {
callback && callback(results, _options);
}, reviver, options);
2013-11-21 17:00:29 +00:00
}
},
/**
* Takes string corresponding to an SVG document, and parses it into a set of fabric objects
2015-05-22 06:08:20 +00:00
* @memberOf fabric
2013-11-21 17:00:29 +00:00
* @param {String} string
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
2017-03-25 17:44:09 +00:00
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
2013-11-21 17:00:29 +00:00
*/
2017-03-25 17:44:09 +00:00
loadSVGFromString: function(string, callback, reviver, options) {
2013-11-21 17:00:29 +00:00
string = string.trim();
var doc;
if (typeof DOMParser !== 'undefined') {
var parser = new DOMParser();
if (parser && parser.parseFromString) {
doc = parser.parseFromString(string, 'text/xml');
}
}
else if (fabric.window.ActiveXObject) {
doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = 'false';
// IE chokes on DOCTYPE
doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
2013-11-21 17:00:29 +00:00
}
2017-03-25 17:44:09 +00:00
fabric.parseSVGDocument(doc.documentElement, function (results, _options) {
callback(results, _options);
}, reviver, options);
2013-11-21 17:00:29 +00:00
}
2010-06-09 22:34:55 +00:00
});
})(typeof exports !== 'undefined' ? exports : this);