Build dist

This commit is contained in:
kangax 2014-11-01 17:41:08 +01:00
parent 70c6d5b030
commit 2534e0db5d
4 changed files with 745 additions and 349 deletions

540
dist/fabric.js vendored
View file

@ -15,7 +15,9 @@ else {
fabric.document = require("jsdom")
.jsdom("<!DOCTYPE html><html><head></head><body></body></html>");
fabric.window = fabric.document.createWindow();
if (fabric.document.createWindow) {
fabric.window = fabric.document.createWindow();
}
}
/**
@ -31,7 +33,6 @@ fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
typeof window === 'undefined';
/**
* Attributes parsed from all SVG elements
* @type array
@ -50,6 +51,7 @@ fabric.SHARED_ATTRIBUTES = [
* Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
*/
fabric.DPI = 96;
fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
(function() {
@ -466,10 +468,12 @@ fabric.Collection = {
* @param {Number|String} value number to operate on
* @return {Number|String}
*/
parseUnit: function(value) {
parseUnit: function(value, fontSize) {
var unit = /\D{0,2}$/.exec(value),
number = parseFloat(value);
if (!fontSize) {
fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
}
switch (unit[0]) {
case 'mm':
return number * fabric.DPI / 25.4;
@ -486,6 +490,9 @@ fabric.Collection = {
case 'pc':
return number * fabric.DPI / 72 * 12; // or * 16
case 'em':
return number * fontSize;
default:
return number;
}
@ -2871,7 +2878,7 @@ if (typeof console !== 'undefined') {
return attr;
}
function normalizeValue(attr, value, parentAttributes) {
function normalizeValue(attr, value, parentAttributes, fontSize) {
var isArray = Object.prototype.toString.call(value) === '[object Array]',
parsed;
@ -2903,7 +2910,7 @@ if (typeof console !== 'undefined') {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
else {
parsed = isArray ? value.map(parseUnit) : parseUnit(value);
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
return (!isArray && isNaN(parsed) ? value : parsed);
@ -2982,7 +2989,7 @@ if (typeof console !== 'undefined') {
],
// == begin transform regexp
number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
number = fabric.reNum,
commaWsp = '(?:\\s+,?\\s*|,\\s*)',
@ -3085,40 +3092,6 @@ if (typeof console !== 'undefined') {
};
})();
function parseFontDeclaration(value, oStyle) {
// TODO: support non-px font size
var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);
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 = parseFloat(fontSize);
}
if (fontFamily) {
oStyle.fontFamily = fontFamily;
}
if (lineHeight) {
oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
}
}
/**
* @private
*/
@ -3130,12 +3103,7 @@ if (typeof console !== 'undefined') {
attr = normalizeAttr(pair[0].trim().toLowerCase());
value = normalizeValue(attr, pair[1].trim());
if (attr === 'font') {
parseFontDeclaration(value, oStyle);
}
else {
oStyle[attr] = value;
}
oStyle[attr] = value;
});
}
@ -3152,12 +3120,7 @@ if (typeof console !== 'undefined') {
attr = normalizeAttr(prop.toLowerCase());
value = normalizeValue(attr, style[prop]);
if (attr === 'font') {
parseFontDeclaration(value, oStyle);
}
else {
oStyle[attr] = value;
}
oStyle[attr] = value;
}
}
@ -3236,7 +3199,7 @@ if (typeof console !== 'undefined') {
x = el.getAttribute('x') || 0,
y = el.getAttribute('y') || 0,
el2 = doc.getElementById(xlink).cloneNode(true),
currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
parentNode;
for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
@ -3246,7 +3209,7 @@ if (typeof console !== 'undefined') {
}
if (attr.nodeName === 'transform') {
currentTrans = currentTrans + ' ' + attr.nodeValue;
currentTrans = attr.nodeValue + ' ' + currentTrans;
}
else {
el2.setAttribute(attr.nodeName, attr.nodeValue);
@ -3254,6 +3217,7 @@ if (typeof console !== 'undefined') {
}
el2.setAttribute('transform', currentTrans);
el2.setAttribute('instantiated_by_use', '1');
el2.removeAttribute('id');
parentNode = el.parentNode;
parentNode.replaceChild(el2, el);
@ -3261,28 +3225,67 @@ if (typeof console !== 'undefined') {
}
/**
* Add a <g> element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements
* Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
*/
function addSvgTransform(doc, matrix) {
matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]);
if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) {
function addVBTransform(element, widthAttr, heightAttr) {
// 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*' +
'$'
),
viewBoxAttr = element.getAttribute('viewBox'),
scaleX = 1, scaleY = 1, minX = 0, minY = 0,
viewBoxWidth, viewBoxHeight, matrix, el;
if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
minX = -parseFloat(viewBoxAttr[1]),
minY = -parseFloat(viewBoxAttr[2]),
viewBoxWidth = parseFloat(viewBoxAttr[3]),
viewBoxHeight = parseFloat(viewBoxAttr[4]);
}
else {
return;
}
if (widthAttr && widthAttr !== viewBoxWidth) {
scaleX = widthAttr / viewBoxWidth;
}
if (heightAttr && heightAttr !== viewBoxHeight) {
scaleY = heightAttr / viewBoxHeight;
}
// default is to preserve aspect ratio
// preserveAspectRatio attribute to be implemented
var el = doc.ownerDocument.createElement('g');
while (doc.firstChild != null) {
el.appendChild(doc.firstChild);
}
el.setAttribute('transform',
'matrix(' + matrix[0] + ' ' +
matrix[1] + ' ' +
matrix[2] + ' ' +
matrix[3] + ' ' +
matrix[4] + ' ' +
matrix[5] + ')');
scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
doc.appendChild(el);
if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) {
return;
}
matrix = 'matrix(' + scaleX +
' 0' +
' 0 ' +
scaleY + ' ' +
(minX * scaleX) + ' ' +
(minY * scaleY) + ')';
if (element.tagName === 'svg') {
el = element.ownerDocument.createElement('g');
while (element.firstChild != null) {
el.appendChild(element.firstChild);
}
element.appendChild(el);
}
else {
el = element;
matrix += el.getAttribute('transform');
}
el.setAttribute('transform', matrix);
}
/**
@ -3297,25 +3300,11 @@ if (typeof console !== 'undefined') {
fabric.parseSVGDocument = (function() {
var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// \d doesn't quite cut it (as we need to match an actual float number)
// matches, e.g.: +14.56e-12, etc.
reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
reViewBoxAttrValue = new RegExp(
'^' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*' +
'$'
);
reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/;
function hasAncestorWithNodeName(element, nodeName) {
while (element && (element = element.parentNode)) {
if (nodeName.test(element.nodeName)) {
if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) {
return true;
}
}
@ -3326,34 +3315,19 @@ if (typeof console !== 'undefined') {
if (!doc) {
return;
}
var startTime = new Date(),
svgUid = fabric.Object.__uid++;
parseUseDirectives(doc);
var startTime = new Date(),
svgUid = fabric.Object.__uid++,
/* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
* as per spec, width and height attributes are to be considered
* 100% if no value is specified.
*/
var viewBoxAttr = doc.getAttribute('viewBox'),
widthAttr = parseUnit(doc.getAttribute('width') || '100%'),
heightAttr = parseUnit(doc.getAttribute('height') || '100%'),
viewBoxWidth,
viewBoxHeight;
heightAttr = parseUnit(doc.getAttribute('height') || '100%');
if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
var minX = parseFloat(viewBoxAttr[1]),
minY = parseFloat(viewBoxAttr[2]),
scaleX = 1, scaleY = 1;
viewBoxWidth = parseFloat(viewBoxAttr[3]);
viewBoxHeight = parseFloat(viewBoxAttr[4]);
if (widthAttr && widthAttr !== viewBoxWidth ) {
scaleX = widthAttr / viewBoxWidth;
}
if (heightAttr && heightAttr !== viewBoxHeight) {
scaleY = heightAttr / viewBoxHeight;
}
addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]);
}
addVBTransform(doc, widthAttr, heightAttr);
var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
@ -3369,8 +3343,9 @@ if (typeof console !== 'undefined') {
}
var elements = descendants.filter(function(el) {
reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0);
return reAllowedSVGTagNames.test(el.tagName) &&
!hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
!hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) {
@ -3379,8 +3354,8 @@ if (typeof console !== 'undefined') {
}
var options = {
width: widthAttr ? widthAttr : viewBoxWidth,
height: heightAttr ? heightAttr : viewBoxHeight,
width: widthAttr,
height: heightAttr,
widthAttr: widthAttr,
heightAttr: heightAttr,
svgUid: svgUid
@ -3389,7 +3364,6 @@ if (typeof console !== 'undefined') {
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
fabric.documentParsingTime = new Date() - startTime;
if (callback) {
@ -3457,6 +3431,48 @@ if (typeof console !== 'undefined') {
}
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 fontDeclaration = '(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+(.*)',
match = value.match(fontDeclaration);
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
@ -3518,7 +3534,8 @@ if (typeof console !== 'undefined') {
}
var value,
parentAttributes = { };
parentAttributes = { },
fontSize;
if (typeof svgUid === 'undefined') {
svgUid = element.getAttribute('svgUid');
@ -3527,12 +3544,14 @@ if (typeof console !== 'undefined') {
if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {
parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
}
fontSize = (parentAttributes && parentAttributes.fontSize ) ||
element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
if (value) {
attr = normalizeAttr(attr);
value = normalizeValue(attr, value, parentAttributes);
value = normalizeValue(attr, value, parentAttributes, fontSize);
memo[attr] = value;
}
@ -3543,7 +3562,9 @@ if (typeof console !== 'undefined') {
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
ownAttributes = extend(ownAttributes,
extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
if (ownAttributes.font) {
fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
}
return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
},
@ -3660,7 +3681,11 @@ if (typeof console !== 'undefined') {
}
rule = match[1];
rule.split(',').forEach(function(_rule) {
allRules[_rule.trim()] = fabric.util.object.clone(ruleObj);
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
}
allRules[_rule] = fabric.util.object.clone(ruleObj);
});
});
}
@ -5356,8 +5381,15 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
var patternSource = typeof this.source === 'function' ? this.source() : this.source,
patternWidth = patternSource.width / object.getWidth(),
patternHeight = patternSource.height / object.getHeight(),
patternOffsetX = this.offsetX / object.getWidth(),
patternOffsetY = this.offsetY / object.getHeight(),
patternImgSrc = '';
if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
patternHeight = 1;
}
if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
patternWidth = 1;
}
if (patternSource.src) {
patternImgSrc = patternSource.src;
}
@ -5366,16 +5398,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
}
return '<pattern id="SVGID_' + this.id +
'" x="' + this.offsetX +
'" y="' + this.offsetY +
'" x="' + patternOffsetX +
'" y="' + patternOffsetY +
'" width="' + patternWidth +
'" height="' + patternHeight + '">' +
'" height="' + patternHeight + '">\n' +
'<image x="0" y="0"' +
' width="' + patternSource.width +
'" height="' + patternSource.height +
'" xlink:href="' + patternImgSrc +
'"></image>' +
'</pattern>';
'"></image>\n' +
'</pattern>\n';
},
/* _TO_SVG_END_ */
@ -5520,23 +5552,31 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* @return {String} SVG representation of a shadow
*/
toSVG: function(object) {
var mode = 'SourceAlpha';
var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40;
if (object && (object.fill === this.color || object.stroke === this.color)) {
mode = 'SourceGraphic';
}
if (object.width && object.height) {
//http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
// we add some extra space to filter box to contain the blur ( 20 )
fBoxX = Math.abs(this.offsetX / object.getWidth()) * 100 + 20;
fBoxY = Math.abs(this.offsetY / object.getHeight()) * 100 + 20;
}
return (
'<filter id="SVGID_' + this.id + '" y="-40%" height="180%">' +
'<feGaussianBlur in="' + mode + '" stdDeviation="' +
'<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
'\t<feGaussianBlur in="' + mode + '" stdDeviation="' +
(this.blur ? this.blur / 3 : 0) +
'"></feGaussianBlur>' +
'<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>' +
'<feMerge>' +
'<feMergeNode></feMergeNode>' +
'<feMergeNode in="SourceGraphic"></feMergeNode>' +
'</feMerge>' +
'</filter>');
'"></feGaussianBlur>\n' +
'\t<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>\n' +
'\t<feMerge>\n' +
'\t\t<feMergeNode></feMergeNode>\n' +
'\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
'\t</feMerge>\n' +
'</filter>\n');
},
/* _TO_SVG_END_ */
@ -5811,6 +5851,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* originX: 'left',
* originY: 'top'
* });
* @example <caption>overlayImage loaded from cross-origin</caption>
* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
* opacity: 0.5,
* angle: 45,
* left: 400,
* top: 400,
* originX: 'left',
* originY: 'top',
* crossOrigin: 'anonymous'
* });
*/
setOverlayImage: function (image, callback, options) {
return this.__setBgOverlayImage('overlayImage', image, callback, options);
@ -5852,6 +5902,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* originX: 'left',
* originY: 'top'
* });
* @example <caption>backgroundImage loaded from cross-origin</caption>
* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
* opacity: 0.5,
* angle: 45,
* left: 400,
* top: 400,
* originX: 'left',
* originY: 'top',
* crossOrigin: 'anonymous'
* });
*/
setBackgroundImage: function (image, callback, options) {
return this.__setBgOverlayImage('backgroundImage', image, callback, options);
@ -5934,7 +5994,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
fabric.util.loadImage(image, function(img) {
this[property] = new fabric.Image(img, options);
callback && callback();
}, this);
}, this, options && options.crossOrigin);
}
else {
this[property] = image;
@ -8332,10 +8392,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
* @private
*/
_setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) {
var target = transform.target, forbidScalingX = false, forbidScalingY = false;
var target = transform.target, forbidScalingX = false, forbidScalingY = false,
strokeWidth = target.stroke ? target.strokeWidth : 0;
transform.newScaleX = localMouse.x / (target.width + target.strokeWidth);
transform.newScaleY = localMouse.y / (target.height + target.strokeWidth);
transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2);
transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2);
if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) {
forbidScalingX = true;
@ -8369,8 +8430,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
_scaleObjectEqually: function(localMouse, target, transform) {
var dist = localMouse.y + localMouse.x,
lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY +
(target.width + (target.strokeWidth)) * transform.original.scaleX;
strokeWidth = target.stroke ? target.strokeWidth : 0,
lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY +
(target.width + (strokeWidth / 2)) * transform.original.scaleX;
// We use transform.scaleX/Y instead of target.scaleX/Y
// because the object may have a min scale and we'll loose the proportions
@ -8481,7 +8543,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
angle = 360 + angle;
}
t.target.angle = angle;
t.target.angle = angle % 360;
},
/**
@ -9811,8 +9873,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
: [ target, this._activeObject ];
return new fabric.Group(groupObjects, {
originX: 'center',
originY: 'center',
canvas: this
});
},
@ -9831,8 +9891,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
}
else if (group.length > 1) {
group = new fabric.Group(group.reverse(), {
originX: 'center',
originY: 'center',
canvas: this
});
group.addWithUpdate();
@ -11413,10 +11471,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return;
}
var mult = this.canvas._currentMultiplier || 1;
ctx.shadowColor = this.shadow.color;
ctx.shadowBlur = this.shadow.blur;
ctx.shadowOffsetX = this.shadow.offsetX;
ctx.shadowOffsetY = this.shadow.offsetY;
ctx.shadowBlur = this.shadow.blur * mult * (this.scaleX + this.scaleY) / 2;
ctx.shadowOffsetX = this.shadow.offsetX * mult * this.scaleX;
ctx.shadowOffsetY = this.shadow.offsetY * mult * this.scaleY;
},
/**
@ -12455,10 +12515,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
h = strokeWidth;
}
if (strokeW) {
w += strokeWidth;
w += w > 0 ? strokeWidth : -strokeWidth;
}
if (strokeH) {
h += strokeWidth;
h += h > 0 ? strokeWidth : -strokeWidth;
}
this.currentWidth = w * this.scaleX;
this.currentHeight = h * this.scaleY;
@ -16412,6 +16472,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this.originalState = { };
this.callSuper('initialize');
if (options.originX) {
this.originX = options.originX;
}
if (options.originY) {
this.originY = options.originY;
}
this._calcBounds();
this._updateObjectsCoords();
@ -16435,13 +16503,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
_updateObjectCoords: function(object) {
var objectLeft = object.getLeft(),
objectTop = object.getTop();
objectTop = object.getTop(),
center = this.getCenterPoint();
object.set({
originalLeft: objectLeft,
originalTop: objectTop,
left: objectLeft - this.left,
top: objectTop - this.top
left: objectLeft - center.x,
top: objectTop - center.y
});
object.setCoords();
@ -16700,14 +16769,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @private
*/
_setObjectPosition: function(object) {
var groupLeft = this.getLeft(),
groupTop = this.getTop(),
var center = this.getCenterPoint(),
rotated = this._getRotatedLeftTop(object);
object.set({
angle: object.getAngle() + this.getAngle(),
left: groupLeft + rotated.left,
top: groupTop + rotated.top,
left: center.x + rotated.left,
top: center.y + rotated.top,
scaleX: object.get('scaleX') * this.get('scaleX'),
scaleY: object.get('scaleY') * this.get('scaleY')
});
@ -16803,8 +16871,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
};
if (!onlyWidthHeight) {
obj.left = (minXY.x + maxXY.x) / 2 || 0;
obj.top = (minXY.y + maxXY.y) / 2 || 0;
obj.left = minXY.x || 0;
obj.top = minXY.y || 0;
if (this.originX === 'center') {
obj.left += obj.width / 2;
}
if (this.originX === 'right') {
obj.left += obj.width;
}
if (this.originY === 'center') {
obj.top += obj.height / 2;
}
if (this.originY === 'bottom') {
obj.top += obj.height;
}
}
return obj;
},
@ -16928,6 +17008,33 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
crossOrigin: '',
/**
* AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
* @type String
* @default
*/
alignX: 'none',
/**
* AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
* @type String
* @default
*/
alignY: 'none',
/**
* meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").
* if meet the image is always fully visibile, if slice the viewport is always filled with image.
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* @type String
* @default
*/
meetOrSlice: 'meet',
/**
* Constructor
* @param {HTMLImageElement | String} element Image element
@ -16964,17 +17071,21 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
* @param {HTMLImageElement} element
* @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
* @param {Object} [options] Options object
* @return {fabric.Image} thisArg
* @chainable
*/
setElement: function(element, callback) {
setElement: function(element, callback, options) {
this._element = element;
this._originalElement = element;
this._initConfig();
this._initConfig(options);
if (this.filters.length !== 0) {
this.applyFilters(callback);
}
else if (callback) {
callback();
}
return this;
},
@ -17049,7 +17160,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
filters: this.filters.map(function(filterObj) {
return filterObj && filterObj.toObject();
}),
crossOrigin: this.crossOrigin
crossOrigin: this.crossOrigin,
alignX: this.alignX,
alignY: this.alignY,
meetOrSlice: this.meetOrSlice
});
},
@ -17060,11 +17174,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
var markup = [], x = -this.width / 2, y = -this.height / 2;
var markup = [], x = -this.width / 2, y = -this.height / 2,
preserveAspectRatio = 'none';
if (this.group && this.group.type === 'path-group') {
x = this.left;
y = this.top;
}
if (this.alignX !== 'none' && this.alignY !== 'none') {
preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
}
markup.push(
'<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
'<image xlink:href="', this.getSvgSrc(),
@ -17075,7 +17193,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
// so that object's center aligns with container's left/top
'" width="', this.width,
'" height="', this.height,
'" preserveAspectRatio="none"',
'" preserveAspectRatio="', preserveAspectRatio, '"',
'></image>\n'
);
@ -17108,6 +17226,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
}
},
/**
* Sets source of an image
* @param {String} src Source string (URL)
* @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
* @param {Object} [options] Options object
* @return {fabric.Image} thisArg
* @chainable
*/
setSrc: function(src, callback, options) {
fabric.util.loadImage(src, function(img) {
return this.setElement(img, callback, options);
}, this, options && options.crossOrigin);
},
/**
* Returns string representation of an instance
* @return {String} String representation of an instance
@ -17133,7 +17265,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @chainable
*/
applyFilters: function(callback) {
if (!this._originalElement) {
return;
}
@ -17187,17 +17318,61 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx, noTransform) {
var x, y, imageMargins = this._findMargins();
x = (noTransform ? this.left : -this.width / 2);
y = (noTransform ? this.top : -this.height / 2);
if (this.meetOrSlice === 'slice') {
ctx.beginPath();
ctx.rect(x, y, this.width, this.height);
ctx.clip();
}
this._element &&
ctx.drawImage(
this._element,
noTransform ? this.left : -this.width/2,
noTransform ? this.top : -this.height/2,
this.width,
this.height
);
ctx.drawImage(this._element,
x + imageMargins.marginX,
y + imageMargins.marginY,
imageMargins.width,
imageMargins.height
);
this._renderStroke(ctx);
},
/**
* @private
*/
_findMargins: function() {
var width = this.width, height = this.height, scales,
scale, marginX = 0, marginY = 0;
if (this.alignX !== 'none' || this.alignY !== 'none') {
scales = [this.width / this._element.width, this.height / this._element.height];
scale = this.meetOrSlice === 'meet'
? Math.min.apply(null, scales) : Math.max.apply(null, scales);
width = this._element.width * scale;
height = this._element.height * scale;
if (this.alignX === 'Mid') {
marginX = (this.width - width) / 2;
}
if (this.alignX === 'Max') {
marginX = this.width - width;
}
if (this.alignY === 'Mid') {
marginY = (this.height - height) / 2;
}
if (this.alignY === 'Max') {
marginY = this.height - height;
}
}
return {
width: width,
height: height,
marginX: marginX,
marginY: marginY
};
},
/**
* @private
*/
@ -17324,7 +17499,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @static
* @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
*/
fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' '));
fabric.Image.ATTRIBUTE_NAMES =
fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' '));
/**
* Returns {@link fabric.Image} instance from an SVG element
@ -17335,8 +17511,29 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {fabric.Image} Instance of fabric.Image
*/
fabric.Image.fromElement = function(element, callback, options) {
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs;
if (parsedAttributes.preserveAspectRatio) {
aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' ');
}
if (aspectRatioAttrs && aspectRatioAttrs.length) {
meetOrSlice = aspectRatioAttrs.pop();
if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
align = meetOrSlice;
meetOrSlice = 'meet';
}
else if (aspectRatioAttrs.length) {
align = aspectRatioAttrs.pop();
}
}
//divide align in alignX and alignY
alignX = align !== 'none' ? align.slice(1, 4) : 'none';
alignY = align !== 'none' ? align.slice(5, 8) : 'none';
parsedAttributes.alignX = alignX;
parsedAttributes.alignY = alignY;
parsedAttributes.meetOrSlice = meetOrSlice;
fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
};
@ -19233,6 +19430,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
*/
_renderText: function(ctx, textLines) {
ctx.save();
this._setOpacity(ctx);
this._setShadow(ctx);
this._setupCompositeOperation(ctx);
this._renderTextFill(ctx, textLines);

14
dist/fabric.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/fabric.min.js.gz vendored

Binary file not shown.

540
dist/fabric.require.js vendored
View file

@ -15,7 +15,9 @@ else {
fabric.document = require("jsdom")
.jsdom("<!DOCTYPE html><html><head></head><body></body></html>");
fabric.window = fabric.document.createWindow();
if (fabric.document.createWindow) {
fabric.window = fabric.document.createWindow();
}
}
/**
@ -31,7 +33,6 @@ fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
typeof window === 'undefined';
/**
* Attributes parsed from all SVG elements
* @type array
@ -50,6 +51,7 @@ fabric.SHARED_ATTRIBUTES = [
* Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
*/
fabric.DPI = 96;
fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
(function() {
@ -466,10 +468,12 @@ fabric.Collection = {
* @param {Number|String} value number to operate on
* @return {Number|String}
*/
parseUnit: function(value) {
parseUnit: function(value, fontSize) {
var unit = /\D{0,2}$/.exec(value),
number = parseFloat(value);
if (!fontSize) {
fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
}
switch (unit[0]) {
case 'mm':
return number * fabric.DPI / 25.4;
@ -486,6 +490,9 @@ fabric.Collection = {
case 'pc':
return number * fabric.DPI / 72 * 12; // or * 16
case 'em':
return number * fontSize;
default:
return number;
}
@ -2871,7 +2878,7 @@ if (typeof console !== 'undefined') {
return attr;
}
function normalizeValue(attr, value, parentAttributes) {
function normalizeValue(attr, value, parentAttributes, fontSize) {
var isArray = Object.prototype.toString.call(value) === '[object Array]',
parsed;
@ -2903,7 +2910,7 @@ if (typeof console !== 'undefined') {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
else {
parsed = isArray ? value.map(parseUnit) : parseUnit(value);
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
return (!isArray && isNaN(parsed) ? value : parsed);
@ -2982,7 +2989,7 @@ if (typeof console !== 'undefined') {
],
// == begin transform regexp
number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
number = fabric.reNum,
commaWsp = '(?:\\s+,?\\s*|,\\s*)',
@ -3085,40 +3092,6 @@ if (typeof console !== 'undefined') {
};
})();
function parseFontDeclaration(value, oStyle) {
// TODO: support non-px font size
var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);
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 = parseFloat(fontSize);
}
if (fontFamily) {
oStyle.fontFamily = fontFamily;
}
if (lineHeight) {
oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
}
}
/**
* @private
*/
@ -3130,12 +3103,7 @@ if (typeof console !== 'undefined') {
attr = normalizeAttr(pair[0].trim().toLowerCase());
value = normalizeValue(attr, pair[1].trim());
if (attr === 'font') {
parseFontDeclaration(value, oStyle);
}
else {
oStyle[attr] = value;
}
oStyle[attr] = value;
});
}
@ -3152,12 +3120,7 @@ if (typeof console !== 'undefined') {
attr = normalizeAttr(prop.toLowerCase());
value = normalizeValue(attr, style[prop]);
if (attr === 'font') {
parseFontDeclaration(value, oStyle);
}
else {
oStyle[attr] = value;
}
oStyle[attr] = value;
}
}
@ -3236,7 +3199,7 @@ if (typeof console !== 'undefined') {
x = el.getAttribute('x') || 0,
y = el.getAttribute('y') || 0,
el2 = doc.getElementById(xlink).cloneNode(true),
currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
parentNode;
for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
@ -3246,7 +3209,7 @@ if (typeof console !== 'undefined') {
}
if (attr.nodeName === 'transform') {
currentTrans = currentTrans + ' ' + attr.nodeValue;
currentTrans = attr.nodeValue + ' ' + currentTrans;
}
else {
el2.setAttribute(attr.nodeName, attr.nodeValue);
@ -3254,6 +3217,7 @@ if (typeof console !== 'undefined') {
}
el2.setAttribute('transform', currentTrans);
el2.setAttribute('instantiated_by_use', '1');
el2.removeAttribute('id');
parentNode = el.parentNode;
parentNode.replaceChild(el2, el);
@ -3261,28 +3225,67 @@ if (typeof console !== 'undefined') {
}
/**
* Add a <g> element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements
* Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
*/
function addSvgTransform(doc, matrix) {
matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]);
if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) {
function addVBTransform(element, widthAttr, heightAttr) {
// 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*' +
'$'
),
viewBoxAttr = element.getAttribute('viewBox'),
scaleX = 1, scaleY = 1, minX = 0, minY = 0,
viewBoxWidth, viewBoxHeight, matrix, el;
if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
minX = -parseFloat(viewBoxAttr[1]),
minY = -parseFloat(viewBoxAttr[2]),
viewBoxWidth = parseFloat(viewBoxAttr[3]),
viewBoxHeight = parseFloat(viewBoxAttr[4]);
}
else {
return;
}
if (widthAttr && widthAttr !== viewBoxWidth) {
scaleX = widthAttr / viewBoxWidth;
}
if (heightAttr && heightAttr !== viewBoxHeight) {
scaleY = heightAttr / viewBoxHeight;
}
// default is to preserve aspect ratio
// preserveAspectRatio attribute to be implemented
var el = doc.ownerDocument.createElement('g');
while (doc.firstChild != null) {
el.appendChild(doc.firstChild);
}
el.setAttribute('transform',
'matrix(' + matrix[0] + ' ' +
matrix[1] + ' ' +
matrix[2] + ' ' +
matrix[3] + ' ' +
matrix[4] + ' ' +
matrix[5] + ')');
scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
doc.appendChild(el);
if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) {
return;
}
matrix = 'matrix(' + scaleX +
' 0' +
' 0 ' +
scaleY + ' ' +
(minX * scaleX) + ' ' +
(minY * scaleY) + ')';
if (element.tagName === 'svg') {
el = element.ownerDocument.createElement('g');
while (element.firstChild != null) {
el.appendChild(element.firstChild);
}
element.appendChild(el);
}
else {
el = element;
matrix += el.getAttribute('transform');
}
el.setAttribute('transform', matrix);
}
/**
@ -3297,25 +3300,11 @@ if (typeof console !== 'undefined') {
fabric.parseSVGDocument = (function() {
var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// \d doesn't quite cut it (as we need to match an actual float number)
// matches, e.g.: +14.56e-12, etc.
reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
reViewBoxAttrValue = new RegExp(
'^' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*,?' +
'\\s*(' + reNum + '+)\\s*' +
'$'
);
reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/;
function hasAncestorWithNodeName(element, nodeName) {
while (element && (element = element.parentNode)) {
if (nodeName.test(element.nodeName)) {
if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) {
return true;
}
}
@ -3326,34 +3315,19 @@ if (typeof console !== 'undefined') {
if (!doc) {
return;
}
var startTime = new Date(),
svgUid = fabric.Object.__uid++;
parseUseDirectives(doc);
var startTime = new Date(),
svgUid = fabric.Object.__uid++,
/* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
* as per spec, width and height attributes are to be considered
* 100% if no value is specified.
*/
var viewBoxAttr = doc.getAttribute('viewBox'),
widthAttr = parseUnit(doc.getAttribute('width') || '100%'),
heightAttr = parseUnit(doc.getAttribute('height') || '100%'),
viewBoxWidth,
viewBoxHeight;
heightAttr = parseUnit(doc.getAttribute('height') || '100%');
if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
var minX = parseFloat(viewBoxAttr[1]),
minY = parseFloat(viewBoxAttr[2]),
scaleX = 1, scaleY = 1;
viewBoxWidth = parseFloat(viewBoxAttr[3]);
viewBoxHeight = parseFloat(viewBoxAttr[4]);
if (widthAttr && widthAttr !== viewBoxWidth ) {
scaleX = widthAttr / viewBoxWidth;
}
if (heightAttr && heightAttr !== viewBoxHeight) {
scaleY = heightAttr / viewBoxHeight;
}
addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]);
}
addVBTransform(doc, widthAttr, heightAttr);
var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
@ -3369,8 +3343,9 @@ if (typeof console !== 'undefined') {
}
var elements = descendants.filter(function(el) {
reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0);
return reAllowedSVGTagNames.test(el.tagName) &&
!hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
!hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) {
@ -3379,8 +3354,8 @@ if (typeof console !== 'undefined') {
}
var options = {
width: widthAttr ? widthAttr : viewBoxWidth,
height: heightAttr ? heightAttr : viewBoxHeight,
width: widthAttr,
height: heightAttr,
widthAttr: widthAttr,
heightAttr: heightAttr,
svgUid: svgUid
@ -3389,7 +3364,6 @@ if (typeof console !== 'undefined') {
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
fabric.documentParsingTime = new Date() - startTime;
if (callback) {
@ -3457,6 +3431,48 @@ if (typeof console !== 'undefined') {
}
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 fontDeclaration = '(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+(.*)',
match = value.match(fontDeclaration);
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
@ -3518,7 +3534,8 @@ if (typeof console !== 'undefined') {
}
var value,
parentAttributes = { };
parentAttributes = { },
fontSize;
if (typeof svgUid === 'undefined') {
svgUid = element.getAttribute('svgUid');
@ -3527,12 +3544,14 @@ if (typeof console !== 'undefined') {
if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {
parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
}
fontSize = (parentAttributes && parentAttributes.fontSize ) ||
element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
if (value) {
attr = normalizeAttr(attr);
value = normalizeValue(attr, value, parentAttributes);
value = normalizeValue(attr, value, parentAttributes, fontSize);
memo[attr] = value;
}
@ -3543,7 +3562,9 @@ if (typeof console !== 'undefined') {
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
ownAttributes = extend(ownAttributes,
extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
if (ownAttributes.font) {
fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
}
return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
},
@ -3660,7 +3681,11 @@ if (typeof console !== 'undefined') {
}
rule = match[1];
rule.split(',').forEach(function(_rule) {
allRules[_rule.trim()] = fabric.util.object.clone(ruleObj);
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
}
allRules[_rule] = fabric.util.object.clone(ruleObj);
});
});
}
@ -5356,8 +5381,15 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
var patternSource = typeof this.source === 'function' ? this.source() : this.source,
patternWidth = patternSource.width / object.getWidth(),
patternHeight = patternSource.height / object.getHeight(),
patternOffsetX = this.offsetX / object.getWidth(),
patternOffsetY = this.offsetY / object.getHeight(),
patternImgSrc = '';
if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
patternHeight = 1;
}
if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
patternWidth = 1;
}
if (patternSource.src) {
patternImgSrc = patternSource.src;
}
@ -5366,16 +5398,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
}
return '<pattern id="SVGID_' + this.id +
'" x="' + this.offsetX +
'" y="' + this.offsetY +
'" x="' + patternOffsetX +
'" y="' + patternOffsetY +
'" width="' + patternWidth +
'" height="' + patternHeight + '">' +
'" height="' + patternHeight + '">\n' +
'<image x="0" y="0"' +
' width="' + patternSource.width +
'" height="' + patternSource.height +
'" xlink:href="' + patternImgSrc +
'"></image>' +
'</pattern>';
'"></image>\n' +
'</pattern>\n';
},
/* _TO_SVG_END_ */
@ -5520,23 +5552,31 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* @return {String} SVG representation of a shadow
*/
toSVG: function(object) {
var mode = 'SourceAlpha';
var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40;
if (object && (object.fill === this.color || object.stroke === this.color)) {
mode = 'SourceGraphic';
}
if (object.width && object.height) {
//http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
// we add some extra space to filter box to contain the blur ( 20 )
fBoxX = Math.abs(this.offsetX / object.getWidth()) * 100 + 20;
fBoxY = Math.abs(this.offsetY / object.getHeight()) * 100 + 20;
}
return (
'<filter id="SVGID_' + this.id + '" y="-40%" height="180%">' +
'<feGaussianBlur in="' + mode + '" stdDeviation="' +
'<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
'\t<feGaussianBlur in="' + mode + '" stdDeviation="' +
(this.blur ? this.blur / 3 : 0) +
'"></feGaussianBlur>' +
'<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>' +
'<feMerge>' +
'<feMergeNode></feMergeNode>' +
'<feMergeNode in="SourceGraphic"></feMergeNode>' +
'</feMerge>' +
'</filter>');
'"></feGaussianBlur>\n' +
'\t<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>\n' +
'\t<feMerge>\n' +
'\t\t<feMergeNode></feMergeNode>\n' +
'\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
'\t</feMerge>\n' +
'</filter>\n');
},
/* _TO_SVG_END_ */
@ -5811,6 +5851,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* originX: 'left',
* originY: 'top'
* });
* @example <caption>overlayImage loaded from cross-origin</caption>
* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
* opacity: 0.5,
* angle: 45,
* left: 400,
* top: 400,
* originX: 'left',
* originY: 'top',
* crossOrigin: 'anonymous'
* });
*/
setOverlayImage: function (image, callback, options) {
return this.__setBgOverlayImage('overlayImage', image, callback, options);
@ -5852,6 +5902,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* originX: 'left',
* originY: 'top'
* });
* @example <caption>backgroundImage loaded from cross-origin</caption>
* canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
* opacity: 0.5,
* angle: 45,
* left: 400,
* top: 400,
* originX: 'left',
* originY: 'top',
* crossOrigin: 'anonymous'
* });
*/
setBackgroundImage: function (image, callback, options) {
return this.__setBgOverlayImage('backgroundImage', image, callback, options);
@ -5934,7 +5994,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
fabric.util.loadImage(image, function(img) {
this[property] = new fabric.Image(img, options);
callback && callback();
}, this);
}, this, options && options.crossOrigin);
}
else {
this[property] = image;
@ -8332,10 +8392,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
* @private
*/
_setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) {
var target = transform.target, forbidScalingX = false, forbidScalingY = false;
var target = transform.target, forbidScalingX = false, forbidScalingY = false,
strokeWidth = target.stroke ? target.strokeWidth : 0;
transform.newScaleX = localMouse.x / (target.width + target.strokeWidth);
transform.newScaleY = localMouse.y / (target.height + target.strokeWidth);
transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2);
transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2);
if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) {
forbidScalingX = true;
@ -8369,8 +8430,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
_scaleObjectEqually: function(localMouse, target, transform) {
var dist = localMouse.y + localMouse.x,
lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY +
(target.width + (target.strokeWidth)) * transform.original.scaleX;
strokeWidth = target.stroke ? target.strokeWidth : 0,
lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY +
(target.width + (strokeWidth / 2)) * transform.original.scaleX;
// We use transform.scaleX/Y instead of target.scaleX/Y
// because the object may have a min scale and we'll loose the proportions
@ -8481,7 +8543,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
angle = 360 + angle;
}
t.target.angle = angle;
t.target.angle = angle % 360;
},
/**
@ -9811,8 +9873,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
: [ target, this._activeObject ];
return new fabric.Group(groupObjects, {
originX: 'center',
originY: 'center',
canvas: this
});
},
@ -9831,8 +9891,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
}
else if (group.length > 1) {
group = new fabric.Group(group.reverse(), {
originX: 'center',
originY: 'center',
canvas: this
});
group.addWithUpdate();
@ -11413,10 +11471,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
return;
}
var mult = this.canvas._currentMultiplier || 1;
ctx.shadowColor = this.shadow.color;
ctx.shadowBlur = this.shadow.blur;
ctx.shadowOffsetX = this.shadow.offsetX;
ctx.shadowOffsetY = this.shadow.offsetY;
ctx.shadowBlur = this.shadow.blur * mult * (this.scaleX + this.scaleY) / 2;
ctx.shadowOffsetX = this.shadow.offsetX * mult * this.scaleX;
ctx.shadowOffsetY = this.shadow.offsetY * mult * this.scaleY;
},
/**
@ -12455,10 +12515,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
h = strokeWidth;
}
if (strokeW) {
w += strokeWidth;
w += w > 0 ? strokeWidth : -strokeWidth;
}
if (strokeH) {
h += strokeWidth;
h += h > 0 ? strokeWidth : -strokeWidth;
}
this.currentWidth = w * this.scaleX;
this.currentHeight = h * this.scaleY;
@ -16412,6 +16472,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this.originalState = { };
this.callSuper('initialize');
if (options.originX) {
this.originX = options.originX;
}
if (options.originY) {
this.originY = options.originY;
}
this._calcBounds();
this._updateObjectsCoords();
@ -16435,13 +16503,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
_updateObjectCoords: function(object) {
var objectLeft = object.getLeft(),
objectTop = object.getTop();
objectTop = object.getTop(),
center = this.getCenterPoint();
object.set({
originalLeft: objectLeft,
originalTop: objectTop,
left: objectLeft - this.left,
top: objectTop - this.top
left: objectLeft - center.x,
top: objectTop - center.y
});
object.setCoords();
@ -16700,14 +16769,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @private
*/
_setObjectPosition: function(object) {
var groupLeft = this.getLeft(),
groupTop = this.getTop(),
var center = this.getCenterPoint(),
rotated = this._getRotatedLeftTop(object);
object.set({
angle: object.getAngle() + this.getAngle(),
left: groupLeft + rotated.left,
top: groupTop + rotated.top,
left: center.x + rotated.left,
top: center.y + rotated.top,
scaleX: object.get('scaleX') * this.get('scaleX'),
scaleY: object.get('scaleY') * this.get('scaleY')
});
@ -16803,8 +16871,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
};
if (!onlyWidthHeight) {
obj.left = (minXY.x + maxXY.x) / 2 || 0;
obj.top = (minXY.y + maxXY.y) / 2 || 0;
obj.left = minXY.x || 0;
obj.top = minXY.y || 0;
if (this.originX === 'center') {
obj.left += obj.width / 2;
}
if (this.originX === 'right') {
obj.left += obj.width;
}
if (this.originY === 'center') {
obj.top += obj.height / 2;
}
if (this.originY === 'bottom') {
obj.top += obj.height;
}
}
return obj;
},
@ -16928,6 +17008,33 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
*/
crossOrigin: '',
/**
* AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
* @type String
* @default
*/
alignX: 'none',
/**
* AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
* @type String
* @default
*/
alignY: 'none',
/**
* meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").
* if meet the image is always fully visibile, if slice the viewport is always filled with image.
* @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
* @type String
* @default
*/
meetOrSlice: 'meet',
/**
* Constructor
* @param {HTMLImageElement | String} element Image element
@ -16964,17 +17071,21 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
* @param {HTMLImageElement} element
* @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
* @param {Object} [options] Options object
* @return {fabric.Image} thisArg
* @chainable
*/
setElement: function(element, callback) {
setElement: function(element, callback, options) {
this._element = element;
this._originalElement = element;
this._initConfig();
this._initConfig(options);
if (this.filters.length !== 0) {
this.applyFilters(callback);
}
else if (callback) {
callback();
}
return this;
},
@ -17049,7 +17160,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
filters: this.filters.map(function(filterObj) {
return filterObj && filterObj.toObject();
}),
crossOrigin: this.crossOrigin
crossOrigin: this.crossOrigin,
alignX: this.alignX,
alignY: this.alignY,
meetOrSlice: this.meetOrSlice
});
},
@ -17060,11 +17174,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
var markup = [], x = -this.width / 2, y = -this.height / 2;
var markup = [], x = -this.width / 2, y = -this.height / 2,
preserveAspectRatio = 'none';
if (this.group && this.group.type === 'path-group') {
x = this.left;
y = this.top;
}
if (this.alignX !== 'none' && this.alignY !== 'none') {
preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
}
markup.push(
'<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
'<image xlink:href="', this.getSvgSrc(),
@ -17075,7 +17193,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
// so that object's center aligns with container's left/top
'" width="', this.width,
'" height="', this.height,
'" preserveAspectRatio="none"',
'" preserveAspectRatio="', preserveAspectRatio, '"',
'></image>\n'
);
@ -17108,6 +17226,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
}
},
/**
* Sets source of an image
* @param {String} src Source string (URL)
* @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
* @param {Object} [options] Options object
* @return {fabric.Image} thisArg
* @chainable
*/
setSrc: function(src, callback, options) {
fabric.util.loadImage(src, function(img) {
return this.setElement(img, callback, options);
}, this, options && options.crossOrigin);
},
/**
* Returns string representation of an instance
* @return {String} String representation of an instance
@ -17133,7 +17265,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @chainable
*/
applyFilters: function(callback) {
if (!this._originalElement) {
return;
}
@ -17187,17 +17318,61 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx, noTransform) {
var x, y, imageMargins = this._findMargins();
x = (noTransform ? this.left : -this.width / 2);
y = (noTransform ? this.top : -this.height / 2);
if (this.meetOrSlice === 'slice') {
ctx.beginPath();
ctx.rect(x, y, this.width, this.height);
ctx.clip();
}
this._element &&
ctx.drawImage(
this._element,
noTransform ? this.left : -this.width/2,
noTransform ? this.top : -this.height/2,
this.width,
this.height
);
ctx.drawImage(this._element,
x + imageMargins.marginX,
y + imageMargins.marginY,
imageMargins.width,
imageMargins.height
);
this._renderStroke(ctx);
},
/**
* @private
*/
_findMargins: function() {
var width = this.width, height = this.height, scales,
scale, marginX = 0, marginY = 0;
if (this.alignX !== 'none' || this.alignY !== 'none') {
scales = [this.width / this._element.width, this.height / this._element.height];
scale = this.meetOrSlice === 'meet'
? Math.min.apply(null, scales) : Math.max.apply(null, scales);
width = this._element.width * scale;
height = this._element.height * scale;
if (this.alignX === 'Mid') {
marginX = (this.width - width) / 2;
}
if (this.alignX === 'Max') {
marginX = this.width - width;
}
if (this.alignY === 'Mid') {
marginY = (this.height - height) / 2;
}
if (this.alignY === 'Max') {
marginY = this.height - height;
}
}
return {
width: width,
height: height,
marginX: marginX,
marginY: marginY
};
},
/**
* @private
*/
@ -17324,7 +17499,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @static
* @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
*/
fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' '));
fabric.Image.ATTRIBUTE_NAMES =
fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' '));
/**
* Returns {@link fabric.Image} instance from an SVG element
@ -17335,8 +17511,29 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @return {fabric.Image} Instance of fabric.Image
*/
fabric.Image.fromElement = function(element, callback, options) {
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs;
if (parsedAttributes.preserveAspectRatio) {
aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' ');
}
if (aspectRatioAttrs && aspectRatioAttrs.length) {
meetOrSlice = aspectRatioAttrs.pop();
if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
align = meetOrSlice;
meetOrSlice = 'meet';
}
else if (aspectRatioAttrs.length) {
align = aspectRatioAttrs.pop();
}
}
//divide align in alignX and alignY
alignX = align !== 'none' ? align.slice(1, 4) : 'none';
alignY = align !== 'none' ? align.slice(5, 8) : 'none';
parsedAttributes.alignX = alignX;
parsedAttributes.alignY = alignY;
parsedAttributes.meetOrSlice = meetOrSlice;
fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
};
@ -19233,6 +19430,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
*/
_renderText: function(ctx, textLines) {
ctx.save();
this._setOpacity(ctx);
this._setShadow(ctx);
this._setupCompositeOperation(ctx);
this._renderTextFill(ctx, textLines);