=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.selectionStart=t}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.selectionStart=n}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[t]?this._setSVGTextLineChars(e,t,n,r,i,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i,s){var o=t===0||this.useNative?"y":"dy",u=e.split(""),a=0,f=this._getSVGLineLeftOffset(t),l=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t);for(var h=0,p=u.length;h'].join("")},_createTextCharSpan:function(e,t,n,r,i,s){var o=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e),""].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.port===443?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function requestFs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){function r(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)}var i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?requestFs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?requestFs(e,function(e){fabric.loadSVGFromString(e.toString(),t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t,n,r){r=r||n;var i=fabric.document.createElement("canvas"),s=new Canvas(e||600,t||600,r);i.style={},i.width=s.width,i.height=s.height;var o=fabric.Canvas||fabric.StaticCanvas,u=new o(i,n);return u.contextContainer=s.getContext("2d"),u.nodeCanvas=s,u.Font=Canvas.Font,u},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this,e),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e){return origSetHeight.call(this,e),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}();
\ No newline at end of file
diff --git a/dist/fabric.min.js.gz b/dist/fabric.min.js.gz
index 807e2c50..e27e8573 100644
Binary files a/dist/fabric.min.js.gz and b/dist/fabric.min.js.gz differ
diff --git a/dist/fabric.require.js b/dist/fabric.require.js
index 0b245194..92fbbbda 100644
--- a/dist/fabric.require.js
+++ b/dist/fabric.require.js
@@ -630,12 +630,7 @@ fabric.Collection = {
groupSVGElements: function(elements, options, path) {
var object;
- if (elements.length > 1) {
- object = new fabric.PathGroup(elements, options);
- }
- else {
- object = elements[0];
- }
+ object = new fabric.PathGroup(elements, options);
if (typeof path !== 'undefined') {
object.setSourcePath(path);
@@ -3111,6 +3106,25 @@ if (typeof console !== 'undefined') {
}
}
+ /**
+ * Add a element that envelop all SCG 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)) return;
+ // default is to preserve aspect ratio
+ // preserveAspectRatio attribute to be implemented
+ matrix[4] *= matrix[0];
+ matrix[5] *= matrix[3];
+ var el = document.createElement('g');
+ while (doc.firstChild != null) {
+ var node = doc.firstChild;
+ el.appendChild(node);
+ }
+ el.setAttribute('transform','matrix(' + matrix[0] + ' ' + matrix[1] + ' ' + matrix[2] + ' ' + matrix[3] + ' ' + matrix[4] + ' ' + matrix[5] + ')');
+ doc.appendChild(el);
+ }
+
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @static
@@ -3154,6 +3168,27 @@ if (typeof console !== 'undefined') {
parseUseDirectives(doc);
+ var viewBoxAttr = doc.getAttribute('viewBox'),
+ widthAttr = parseFloat(doc.getAttribute('width')),
+ heightAttr = parseFloat(doc.getAttribute('height')),
+ viewBoxWidth,
+ viewBoxHeight;
+
+ 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, -minX, -minY]);
+ }
+
var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
if (descendants.length === 0 && fabric.isLikelyNode) {
@@ -3177,42 +3212,15 @@ if (typeof console !== 'undefined') {
return;
}
- var viewBoxAttr = doc.getAttribute('viewBox'),
- widthAttr = parseFloat(doc.getAttribute('width')),
- heightAttr = parseFloat(doc.getAttribute('height')),
- width = null,
- height = null,
- viewBoxWidth,
- viewBoxHeight,
- minX,
- minY;
-
- if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
- minX = parseFloat(viewBoxAttr[1]);
- minY = parseFloat(viewBoxAttr[2]);
- viewBoxWidth = parseFloat(viewBoxAttr[3]);
- viewBoxHeight = parseFloat(viewBoxAttr[4]);
- }
-
- if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) {
- width = viewBoxWidth;
- height = viewBoxHeight;
- }
- else {
- // values of width/height attributes overwrite those extracted from viewbox attribute
- width = widthAttr ? widthAttr : viewBoxWidth;
- height = heightAttr ? heightAttr : viewBoxHeight;
- }
-
var options = {
- width: width,
- height: height,
+ width: widthAttr ? widthAttr : viewBoxWidth,
+ height: heightAttr ? heightAttr : viewBoxHeight,
widthAttr: widthAttr,
heightAttr: heightAttr
};
- fabric.gradientDefs = fabric.getGradientDefs(doc);
- fabric.cssRules = fabric.getCSSRules(doc);
+ fabric.gradientDefs = extend(fabric.getGradientDefs(doc), fabric.gradientDefs);
+ fabric.cssRules = extend(fabric.getCSSRules(doc), fabric.cssRules);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
@@ -3662,7 +3670,7 @@ fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
else {
var obj = klass.fromElement(el, this.options);
this.reviver && this.reviver(el, obj);
- this.instances.splice(index, 0, obj);
+ this.instances[index] = obj;
this.checkIfDone();
}
};
@@ -3671,7 +3679,7 @@ fabric.ElementsParser.prototype.createCallback = function(index, el) {
var _this = this;
return function(obj) {
_this.reviver && _this.reviver(el, obj);
- _this.instances.splice(index, 0, obj);
+ _this.instances[index] = obj;
_this.checkIfDone();
};
};
@@ -11125,7 +11133,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderStroke: function(ctx) {
- if (!this.stroke) return;
+ if (!this.stroke || this.strokeWidth === 0) return;
ctx.save();
if (this.strokeDashArray) {
@@ -13331,16 +13339,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
ctx.beginPath();
var isInPathGroup = this.group && this.group.type === 'path-group';
- if (isInPathGroup && !this.transformMatrix) {
+ if (isInPathGroup) {
// Line coords are distances from left-top of canvas to origin of line.
//
// To render line in a path-group, we need to translate them to
// distances from center of path-group to center of line.
var cp = this.getCenterPoint();
ctx.translate(
- -this.group.width/2 + cp.x,
- -this.group.height / 2 + cp.y
+ cp.x,
+ cp.y
);
+ if (!this.transformMatrix) ctx.translate(-this.group.width / 2, -this.group.height / 2);
}
if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
@@ -14488,8 +14497,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
var points = fabric.parsePointsAttribute(element.getAttribute('points')),
parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);
- fabric.util.normalizePoints(points, options);
-
+ if (!('transformMatrix' in parsedAttributes)) {
+ fabric.util.normalizePoints(points, options);
+ }
return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true);
};
/* _FROM_SVG_END_ */
@@ -14698,8 +14708,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
var points = fabric.parsePointsAttribute(element.getAttribute('points')),
parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);
- fabric.util.normalizePoints(points, options);
-
+ if (!('transformMatrix' in parsedAttributes)) {
+ fabric.util.normalizePoints(points, options);
+ }
return new fabric.Polygon(points, extend(parsedAttributes, options), true);
};
/* _FROM_SVG_END_ */
@@ -14876,7 +14887,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @private
* @param {CanvasRenderingContext2D} ctx context to render path on
*/
- _render: function(ctx) {
+ _render: function(ctx, noTransform) {
var current, // current instruction
previous = null,
subpathStartX = 0,
@@ -14892,6 +14903,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
l = -((this.width / 2) + this.pathOffset.x),
t = -((this.height / 2) + this.pathOffset.y);
+ if (noTransform) {
+ l += this.width / 2;
+ t += this.height / 2;
+ }
+
for (var i = 0, len = this.path.length; i < len; ++i) {
current = this.path[i];
@@ -15173,6 +15189,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
if (!this.visible) return;
ctx.save();
+ if (noTransform) {
+ ctx.translate(-this.width/2, -this.height/2);
+ }
var m = this.transformMatrix;
if (m) {
@@ -15187,7 +15206,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
this.clipTo && fabric.util.clipContext(this, ctx);
ctx.beginPath();
ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
- this._render(ctx);
+ this._render(ctx, noTransform);
this._renderFill(ctx);
this._renderStroke(ctx);
this.clipTo && ctx.restore();
@@ -18438,14 +18457,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
*/
_render: function(ctx) {
- var isInPathGroup = this.group && this.group.type === 'path-group';
- if (isInPathGroup && !this.transformMatrix) {
- ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top);
- }
- else if (isInPathGroup && this.transformMatrix) {
- ctx.translate(-this.group.width/2, -this.group.height/2);
- }
-
if (typeof Cufon === 'undefined' || this.useNative === true) {
this._renderViaNative(ctx);
}
@@ -18461,8 +18472,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
_renderViaNative: function(ctx) {
var textLines = this.text.split(this._reNewline);
- this.transform(ctx, fabric.isLikelyNode);
-
this._setTextStyles(ctx);
this.width = this._getTextWidth(ctx, textLines);
@@ -18676,7 +18685,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
* @param {Array} textLines Array of all text lines
*/
_renderTextStroke: function(ctx, textLines) {
- if (!this.stroke && !this._skipFillStrokeCheck) return;
+ if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) return;
var lineHeights = 0;
@@ -18852,15 +18861,24 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
* Renders text instance on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
- render: function(ctx) {
+ render: function(ctx, noTransform) {
// do not render if object is not visible
if (!this.visible) return;
ctx.save();
+ this._transform(ctx, noTransform);
+
var m = this.transformMatrix;
- if (m && (!this.group || this.group.type === 'path-group')) {
+ var isInPathGroup = this.group && this.group.type === 'path-group';
+ if (isInPathGroup) {
+ ctx.translate(-this.group.width/2, -this.group.height/2);
+ }
+ if (m) {
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
+ if (isInPathGroup) {
+ ctx.translate(this.left, this.top);
+ }
this._render(ctx);
ctx.restore();
},
@@ -19175,7 +19193,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
}
if (!options.originX) {
- options.originX = 'center';
+ options.originX = 'left';
}
var text = new fabric.Text(element.textContent, options);
@@ -19185,9 +19203,15 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag
x/y attributes in SVG correspond to the bottom-left corner of text bounding box
top/left properties in Fabric correspond to center point of text bounding box
*/
-
+ var offX = 0;
+ if (text.originX === 'left') {
+ offX = text.getWidth() / 2;
+ }
+ if (text.originX === 'right') {
+ offX = -text.getWidth() / 2;
+ }
text.set({
- left: text.getLeft() + text.getWidth() / 2,
+ left: text.getLeft() + offX,
top: text.getTop() - text.getHeight() / 2
});