fabric-pure-browser
Version:
Fabric.js package with no node-specific dependencies (node-canvas, jsdom). The project is published once a day (in case if a new version appears) from 'master' branch of https://github.com/fabricjs/fabric.js repository. You can keep original imports in
1,080 lines (961 loc) • 36.4 kB
JavaScript
(function(global) {
'use strict';
/**
* @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,
svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line',
'image', 'text'],
svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'],
svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'],
svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'],
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',
'letter-spacing': 'charSpacing',
'paint-order': 'paintFirst',
'stroke-dasharray': 'strokeDashArray',
'stroke-dashoffset': 'strokeDashOffset',
'stroke-linecap': 'strokeLineCap',
'stroke-linejoin': 'strokeLineJoin',
'stroke-miterlimit': 'strokeMiterLimit',
'stroke-opacity': 'strokeOpacity',
'stroke-width': 'strokeWidth',
'text-decoration': 'textDecoration',
'text-anchor': 'textAnchor',
opacity: 'opacity',
'clip-path': 'clipPath',
'clip-rule': 'clipRule',
'vector-effect': 'strokeUniform'
},
colorAttributes = {
stroke: 'strokeOpacity',
fill: 'fillOpacity'
},
fSize = 'font-size', cPath = 'clip-path';
fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames);
fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements);
fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors);
fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents);
fabric.cssRules = { };
fabric.gradientDefs = { };
fabric.clipPaths = { };
function normalizeAttr(attr) {
// transform attribute names
if (attr in attributesMap) {
return attributesMap[attr];
}
return attr;
}
function normalizeValue(attr, value, parentAttributes, fontSize) {
var isArray = Object.prototype.toString.call(value) === '[object Array]',
parsed;
if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
value = '';
}
else if (attr === 'vector-effect') {
value = value === 'non-scaling-stroke';
}
else if (attr === 'strokeDashArray') {
if (value === 'none') {
value = null;
}
else {
value = value.replace(/,/g, ' ').split(/\s+/).map(parseFloat);
}
}
else if (attr === 'transformMatrix') {
if (parentAttributes && parentAttributes.transformMatrix) {
value = multiplyTransformMatrices(
parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
}
else {
value = fabric.parseTransformAttribute(value);
}
}
else if (attr === 'visible') {
value = value !== 'none' && value !== 'hidden';
// display=none on parent element always takes precedence over child element
if (parentAttributes && parentAttributes.visible === false) {
value = false;
}
}
else if (attr === 'opacity') {
value = parseFloat(value);
if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
value *= parentAttributes.opacity;
}
}
else if (attr === 'textAnchor' /* text-anchor */) {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
else if (attr === 'charSpacing') {
// parseUnit returns px and we convert it to em
parsed = parseUnit(value, fontSize) / fontSize * 1000;
}
else if (attr === 'paintFirst') {
var fillIndex = value.indexOf('fill');
var strokeIndex = value.indexOf('stroke');
var value = 'fill';
if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) {
value = 'stroke';
}
else if (fillIndex === -1 && strokeIndex > -1) {
value = 'stroke';
}
}
else if (attr === 'href' || attr === 'xlink:href') {
return value;
}
else {
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
return (!isArray && isNaN(parsed) ? value : parsed);
}
/**
* @private
*/
function getSvgRegex(arr) {
return new RegExp('^(' + arr.join('|') + ')\\b', 'i');
}
/**
* @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, i, len;
for (i = 0, len = nodeNames.length; i < len; i++) {
nodeName = nodeNames[i];
nodeList = doc.getElementsByTagName(nodeName);
nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
}
return nodeArray;
}
/**
* Parses "transform" attribute, returning an array of values
* @static
* @function
* @memberOf fabric
* @param {String} attributeValue String containing attribute value
* @return {Array} Array of 6 elements representing transformation matrix
*/
fabric.parseTransformAttribute = (function() {
function rotateMatrix(matrix, args) {
var cos = fabric.util.cos(args[0]), sin = fabric.util.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);
}
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]));
}
function translateMatrix(matrix, args) {
matrix[4] = args[0];
if (args.length === 2) {
matrix[5] = args[1];
}
}
// identity matrix
var iMatrix = fabric.iMatrix,
// == begin transform regexp
number = fabric.reNum,
commaWsp = '(?:\\s+,?\\s*|,\\s*)',
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + ')' +
commaWsp + '(' + number + '))?\\s*\\))',
scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
matrix = '(?:(matrix)\\s*\\(\\s*' +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' +
'\\s*\\))',
transform = '(?:' +
matrix + '|' +
translate + '|' +
scale + '|' +
rotate + '|' +
skewX + '|' +
skewY +
')',
transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
transformList = '^\\s*(?:' + transforms + '?)\\s*$',
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
reTransformList = new RegExp(transformList),
// == end transform regexp
reTransform = new RegExp(transform, 'g');
return function(attributeValue) {
// start with identity matrix
var matrix = iMatrix.concat(),
matrices = [];
// return if no argument was given or
// an argument does not match transform attribute regexp
if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
return matrix;
}
attributeValue.replace(reTransform, function(match) {
var m = new RegExp(transform).exec(match).filter(function (match) {
// match !== '' && match != null
return (!!match);
}),
operation = m[1],
args = m.slice(2).map(parseFloat);
switch (operation) {
case 'translate':
translateMatrix(matrix, args);
break;
case 'rotate':
args[0] = fabric.util.degreesToRadians(args[0]);
rotateMatrix(matrix, args);
break;
case 'scale':
scaleMatrix(matrix, args);
break;
case 'skewX':
skewMatrix(matrix, args, 2);
break;
case 'skewY':
skewMatrix(matrix, args, 1);
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;
};
})();
/**
* @private
*/
function parseStyleString(style, oStyle) {
var attr, value;
style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
var pair = chunk.split(':');
attr = pair[0].trim().toLowerCase();
value = pair[1].trim();
oStyle[attr] = value;
});
}
/**
* @private
*/
function parseStyleObject(style, oStyle) {
var attr, value;
for (var prop in style) {
if (typeof style[prop] === 'undefined') {
continue;
}
attr = prop.toLowerCase();
value = style[prop];
oStyle[attr] = value;
}
}
/**
* @private
*/
function getGlobalStylesForElement(element, svgUid) {
var styles = { };
for (var rule in fabric.cssRules[svgUid]) {
if (elementMatchesRule(element, rule.split(' '))) {
for (var property in fabric.cssRules[svgUid][rule]) {
styles[property] = fabric.cssRules[svgUid][rule][property];
}
}
}
return styles;
}
/**
* @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;
}
/**
* @private
*/
function selectorMatches(element, selector) {
var nodeName = element.nodeName,
classNames = element.getAttribute('class'),
id = element.getAttribute('id'), matcher, i;
// 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 (i = classNames.length; i--;) {
matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
selector = selector.replace(matcher, '');
}
}
return selector.length === 0;
}
/**
* @private
* to support IE8 missing getElementById on SVGdocument and on node xmlDOM
*/
function elementById(doc, id) {
var el;
doc.getElementById && (el = doc.getElementById(id));
if (el) {
return el;
}
var node, i, len, nodelist = doc.getElementsByTagName('*');
for (i = 0, len = nodelist.length; i < len; i++) {
node = nodelist[i];
if (id === node.getAttribute('id')) {
return node;
}
}
}
/**
* @private
*/
function parseUseDirectives(doc) {
var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
while (nodelist.length && i < nodelist.length) {
var el = nodelist[i],
xlink = (el.getAttribute('xlink:href') || el.getAttribute('href')).substr(1),
x = el.getAttribute('x') || 0,
y = el.getAttribute('y') || 0,
el2 = elementById(doc, xlink).cloneNode(true),
currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
parentNode, oldLength = nodelist.length, attr, j, attrs, len, namespace = fabric.svgNS;
applyViewboxTransform(el2);
if (/^svg$/i.test(el2.nodeName)) {
var el3 = el2.ownerDocument.createElementNS(namespace, 'g');
for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) {
attr = attrs.item(j);
el3.setAttributeNS(namespace, attr.nodeName, attr.nodeValue);
}
// el2.firstChild != null
while (el2.firstChild) {
el3.appendChild(el2.firstChild);
}
el2 = el3;
}
for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) {
attr = attrs.item(j);
if (attr.nodeName === 'x' || attr.nodeName === 'y' ||
attr.nodeName === 'xlink:href' || attr.nodeName === 'href') {
continue;
}
if (attr.nodeName === 'transform') {
currentTrans = attr.nodeValue + ' ' + currentTrans;
}
else {
el2.setAttribute(attr.nodeName, attr.nodeValue);
}
}
el2.setAttribute('transform', currentTrans);
el2.setAttribute('instantiated_by_use', '1');
el2.removeAttribute('id');
parentNode = el.parentNode;
parentNode.replaceChild(el2, el);
// some browsers do not shorten nodelist after replaceChild (IE8)
if (nodelist.length === oldLength) {
i++;
}
}
}
// 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*' +
'$'
);
/**
* Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
*/
function applyViewboxTransform(element) {
var viewBoxAttr = element.getAttribute('viewBox'),
scaleX = 1,
scaleY = 1,
minX = 0,
minY = 0,
viewBoxWidth, viewBoxHeight, matrix, el,
widthAttr = element.getAttribute('width'),
heightAttr = element.getAttribute('height'),
x = element.getAttribute('x') || 0,
y = element.getAttribute('y') || 0,
preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName)
|| !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
toBeParsed = missingViewBox && missingDimAttr,
parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0;
parsedDim.width = 0;
parsedDim.height = 0;
parsedDim.toBeParsed = toBeParsed;
if (toBeParsed) {
return parsedDim;
}
if (missingViewBox) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
return parsedDim;
}
minX = -parseFloat(viewBoxAttr[1]);
minY = -parseFloat(viewBoxAttr[2]);
viewBoxWidth = parseFloat(viewBoxAttr[3]);
viewBoxHeight = parseFloat(viewBoxAttr[4]);
parsedDim.minX = minX;
parsedDim.minY = minY;
parsedDim.viewBoxWidth = viewBoxWidth;
parsedDim.viewBoxHeight = viewBoxHeight;
if (!missingDimAttr) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
scaleX = parsedDim.width / viewBoxWidth;
scaleY = parsedDim.height / viewBoxHeight;
}
else {
parsedDim.width = viewBoxWidth;
parsedDim.height = viewBoxHeight;
}
// default is to preserve aspect ratio
preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
if (preserveAspectRatio.alignX !== 'none') {
//translate all container for the effect of Mid, Min, Max
if (preserveAspectRatio.meetOrSlice === 'meet') {
scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
// calculate additional translation to move the viewbox
}
if (preserveAspectRatio.meetOrSlice === 'slice') {
scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY);
// calculate additional translation to move the viewbox
}
widthDiff = parsedDim.width - viewBoxWidth * scaleX;
heightDiff = parsedDim.height - viewBoxHeight * scaleX;
if (preserveAspectRatio.alignX === 'Mid') {
widthDiff /= 2;
}
if (preserveAspectRatio.alignY === 'Mid') {
heightDiff /= 2;
}
if (preserveAspectRatio.alignX === 'Min') {
widthDiff = 0;
}
if (preserveAspectRatio.alignY === 'Min') {
heightDiff = 0;
}
}
if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
return parsedDim;
}
if (x || y) {
translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
}
matrix = translateMatrix + ' matrix(' + scaleX +
' 0' +
' 0 ' +
scaleY + ' ' +
(minX * scaleX + widthDiff) + ' ' +
(minY * scaleY + heightDiff) + ') ';
parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix);
if (element.nodeName === 'svg') {
el = element.ownerDocument.createElementNS(fabric.svgNS, 'g');
// element.firstChild != null
while (element.firstChild) {
el.appendChild(element.firstChild);
}
element.appendChild(el);
}
else {
el = element;
matrix = el.getAttribute('transform') + matrix;
}
el.setAttribute('transform', matrix);
return parsedDim;
}
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;
}
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @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).
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [parsingOptions] options for parsing document
* @param {String} [parsingOptions.crossOrigin] crossOrigin settings
*/
fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
if (!doc) {
return;
}
parseUseDirectives(doc);
var svgUid = fabric.Object.__uid++, i, len,
options = applyViewboxTransform(doc),
descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
options.crossOrigin = parsingOptions && 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 (i = 0, len = descendants.length; i < len; i++) {
arr[i] = descendants[i];
}
descendants = arr;
}
var elements = descendants.filter(function(el) {
applyViewboxTransform(el);
return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) &&
!hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) {
callback && callback([], {});
return;
}
var clipPaths = { };
descendants.filter(function(el) {
return el.nodeName.replace('svg:', '') === 'clipPath';
}).forEach(function(el) {
var id = el.getAttribute('id');
clipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) {
return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', ''));
});
});
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
fabric.clipPaths[svgUid] = clipPaths;
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances, elements) {
if (callback) {
callback(instances, options, elements, descendants);
delete fabric.gradientDefs[svgUid];
delete fabric.cssRules[svgUid];
delete fabric.clipPaths[svgUid];
}
}, clone(options), reviver, parsingOptions);
};
function recursivelyParseGradientsXlink(doc, gradient) {
var gradientsAttrs = ['gradientTransform', 'x1', 'x2', 'y1', 'y2', 'gradientUnits', 'cx', 'cy', 'r', 'fx', 'fy'],
xlinkAttr = 'xlink:href',
xLink = gradient.getAttribute(xlinkAttr).substr(1),
referencedGradient = elementById(doc, xLink);
if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) {
recursivelyParseGradientsXlink(doc, referencedGradient);
}
gradientsAttrs.forEach(function(attr) {
if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) {
gradient.setAttribute(attr, referencedGradient.getAttribute(attr));
}
});
if (!gradient.children.length) {
var referenceClone = referencedGradient.cloneNode(true);
while (referenceClone.firstChild) {
gradient.appendChild(referenceClone.firstChild);
}
}
gradient.removeAttribute(xlinkAttr);
}
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+(.*)');
extend(fabric, {
/**
* 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) {
var match = value.match(reFontDeclaration);
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;
}
},
/**
* 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, gradientDefs = { };
j = elList.length;
while (j--) {
el = elList[j];
if (el.getAttribute('xlink:href')) {
recursivelyParseGradientsXlink(doc, el);
}
gradientDefs[el.getAttribute('id')] = el;
}
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
*/
parseAttributes: function(element, attributes, svgUid) {
if (!element) {
return;
}
var value,
parentAttributes = { },
fontSize, parentFontSize;
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
if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) {
parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
}
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
if (value) { // eslint-disable-line
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)
var cssAttrs = extend(
getGlobalStylesForElement(element, svgUid),
fabric.parseStyleAttribute(element)
);
ownAttributes = extend(
ownAttributes,
cssAttrs
);
if (cssAttrs[cPath]) {
element.setAttribute(cPath, cssAttrs[cPath]);
}
fontSize = parentFontSize = parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE;
if (ownAttributes[fSize]) {
// looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers.
ownAttributes[fSize] = fontSize = parseUnit(ownAttributes[fSize], parentFontSize);
}
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);
}
var mergedAttrs = extend(parentAttributes, normalizedStyle);
return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
},
/**
* 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.
*/
parseElements: function(elements, callback, options, reviver, parsingOptions) {
new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse();
},
/**
* 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;
}
if (typeof style === 'string') {
parseStyleString(style, oStyle);
}
else {
parseStyleObject(style, oStyle);
}
return oStyle;
},
/**
* Parses "points" attribute, returning an array of values
* @static
* @memberOf fabric
* @param {String} points points attribute string
* @return {Array} array of points
*/
parsePointsAttribute: function(points) {
// points attribute is required and must not be empty
if (!points) {
return null;
}
// replace commas with whitespace and remove bookending whitespace
points = points.replace(/,/g, ' ').trim();
points = points.split(/\s+/);
var parsedPoints = [], i, len;
for (i = 0, len = points.length; i < len; i += 2) {
parsedPoints.push({
x: parseFloat(points[i]),
y: parseFloat(points[i + 1])
});
}
// odd number of points is an error
// if (parsedPoints.length % 2 !== 0) {
// return null;
// }
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'), i, len,
allRules = { }, rules;
// very crude parsing of style contents
for (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;
// remove comments
styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
if (styleContents.trim() === '') {
continue;
}
rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
rules = rules.map(function(rule) { return rule.trim(); });
// eslint-disable-next-line no-loop-func
rules.forEach(function(rule) {
var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
ruleObj = { }, declaration = match[2].trim(),
propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
for (i = 0, len = propertyValuePairs.length; i < len; i++) {
var pair = propertyValuePairs[i].split(/\s*:\s*/),
property = pair[0],
value = pair[1];
ruleObj[property] = value;
}
rule = match[1];
rule.split(',').forEach(function(_rule) {
_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);
}
});
});
}
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)
* @memberOf fabric
* @param {String} url
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
*/
loadSVGFromURL: function(url, callback, reviver, options) {
url = url.replace(/^\n\s*/, '').trim();
new fabric.util.request(url, {
method: 'get',
onComplete: onComplete
});
function onComplete(r) {
var xml = r.responseXML;
if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = 'false';
//IE chokes on DOCTYPE
xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
}
if (!xml || !xml.documentElement) {
callback && callback(null);
return false;
}
fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) {
callback && callback(results, _options, elements, allElements);
}, reviver, options);
}
},
/**
* Takes string corresponding to an SVG document, and parses it into a set of fabric objects
* @memberOf fabric
* @param {String} string
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
*/
loadSVGFromString: function(string, callback, reviver, options) {
string = string.trim();
var doc;
if (typeof fabric.window.DOMParser !== 'undefined') {
var parser = new fabric.window.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, ''));
}
fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) {
callback(results, _options, elements, allElements);
}, reviver, options);
}
});
})(typeof exports !== 'undefined' ? exports : this);