From 61249c85a5ee3c65778558fe21a3a17ce47f46e5 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 Oct 2018 19:07:16 +0200 Subject: [PATCH] Version 242 (#5309) * Version 242 * fixed version --- CHANGELOG.md | 6 + HEADER.js | 2 +- dist/fabric.js | 603 ++++++++++++++++++++----------------- package.json | 2 +- test/unit/canvas_static.js | 4 +- 5 files changed, 339 insertions(+), 278 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 156bea65e22..7dd3ac446c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.4.2] +- Fix: Better toSVG support to enable clipPath [#5284](https://github.com/fabricjs/fabric.js/pull/5284) +- Fix: Per pixel target find and groups and sub targets [#5287](https://github.com/fabricjs/fabric.js/pull/5287) +- Fix: Object clone as Image and shadow clipping [#5308](https://github.com/fabricjs/fabric.js/pull/5308) +- Fix: IE11 loading SVG [#5307](https://github.com/fabricjs/fabric.js/pull/5307) + ## [2.4.1] - Fix: Avoid enterEditing if another object is the activeObject [#5261](https://github.com/fabricjs/fabric.js/pull/5261) - Fix: clipPath enliving for Image fromObject [#5279](https://github.com/fabricjs/fabric.js/pull/5279) diff --git a/HEADER.js b/HEADER.js index 1b2fe70f8a2..f465f9c668f 100644 --- a/HEADER.js +++ b/HEADER.js @@ -1,6 +1,6 @@ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: '2.4.1' }; +var fabric = fabric || { version: '2.4.2' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } diff --git a/dist/fabric.js b/dist/fabric.js index f0923458154..507f33f0e38 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -1,7 +1,7 @@ /* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: '2.4.1' }; +var fabric = fabric || { version: '2.4.2' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -1081,7 +1081,7 @@ fabric.CommonMethods = { */ groupSVGElements: function(elements, options, path) { var object; - if (elements.length === 1) { + if (elements && elements.length === 1) { return elements[0]; } if (options) { @@ -2720,24 +2720,6 @@ fabric.CommonMethods = { return url + (/\?/.test(url) ? '&' : '?') + param; } - var makeXHR = (function() { - var factories = [ - function() { return new fabric.window.XMLHttpRequest(); }, - function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } - ]; - for (var i = factories.length; i--; ) { - try { - var req = factories[i](); - if (req) { - return factories[i]; - } - } - catch (err) { } - } - })(); - function emptyFn() { } /** @@ -2756,7 +2738,7 @@ fabric.CommonMethods = { var method = options.method ? options.method.toUpperCase() : 'GET', onComplete = options.onComplete || function() { }, - xhr = makeXHR(), + xhr = new fabric.window.XMLHttpRequest(), body = options.body || options.parameters; /** @ignore */ @@ -3823,7 +3805,6 @@ if (typeof console !== 'undefined') { */ 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), @@ -6555,6 +6536,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp toFixed = fabric.util.toFixed, transformPoint = fabric.util.transformPoint, invertTransform = fabric.util.invertTransform, + getNodeCanvas = fabric.util.getNodeCanvas, + createCanvasElement = fabric.util.createCanvasElement, CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); @@ -6600,6 +6583,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * Backwards incompatibility note: The "backgroundImageOpacity" * and "backgroundImageStretch" properties are deprecated since 1.3.9. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. + * since 2.4.0 image caching is active, please when putting an image as background, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching * @type fabric.Image * @default */ @@ -6620,6 +6606,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * Backwards incompatibility note: The "overlayImageLeft" * and "overlayImageTop" properties are deprecated since 1.3.9. * Use {@link fabric.Image#left} and {@link fabric.Image#top}. + * since 2.4.0 image caching is active, please when putting an image as overlay, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching * @type fabric.Image * @default */ @@ -7000,13 +6989,18 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp __setBgOverlayImage: function(property, image, callback, options) { if (typeof image === 'string') { fabric.util.loadImage(image, function(img) { - img && (this[property] = new fabric.Image(img, options)); + if (img) { + var instance = new fabric.Image(img, options); + this[property] = instance; + instance.canvas = this; + } callback && callback(img); }, this, options && options.crossOrigin); } else { options && image.setOptions(options); this[property] = image; + image && (image.canvas = this); callback && callback(image); } @@ -7031,7 +7025,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @private */ _createCanvasElement: function() { - var element = fabric.util.createCanvasElement(); + var element = createCanvasElement(); if (!element) { throw CANVAS_INIT_ERROR; } @@ -7049,20 +7043,21 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * @param {Object} [options] Options object */ _initOptions: function (options) { + var lowerCanvasEl = this.lowerCanvasEl; this._setOptions(options); - this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; + this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0; if (!this.lowerCanvasEl.style) { return; } - this.lowerCanvasEl.width = this.width; - this.lowerCanvasEl.height = this.height; + lowerCanvasEl.width = this.width; + lowerCanvasEl.height = this.height; - this.lowerCanvasEl.style.width = this.width + 'px'; - this.lowerCanvasEl.style.height = this.height + 'px'; + lowerCanvasEl.style.width = this.width + 'px'; + lowerCanvasEl.style.height = this.height + 'px'; this.viewportTransform = this.viewportTransform.slice(); }, @@ -7815,7 +7810,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp */ toSVG: function(options, reviver) { options || (options = { }); - + options.reviver = reviver; var markup = []; this._setSVGPreamble(markup, options); @@ -7823,9 +7818,13 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp this._setSVGBgOverlayColor(markup, 'backgroundColor'); this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); - + if (this.clipPath) { + markup.push('\n'); + } this._setSVGObjects(markup, reviver); - + if (this.clipPath) { + markup.push('\n'); + } this._setSVGBgOverlayColor(markup, 'overlayColor'); this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); @@ -7888,10 +7887,22 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp '\n', this.createSVGFontFacesMarkup(), this.createSVGRefElementsMarkup(), + this.createSVGClipPathMarkup(options), '\n' ); }, + createSVGClipPathMarkup: function(options) { + var clipPath = this.clipPath; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + return '\n' + + this.clipPath.toClipPathSVG(options.reviver) + + '\n'; + } + return ''; + }, + /** * Creates markup containing SVG referenced elements like patterns, gradients etc. * @return {String} @@ -8310,7 +8321,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * `null` if canvas element or context can not be initialized */ supports: function (methodName) { - var el = fabric.util.createCanvasElement(); + var el = createCanvasElement(); if (!el || !el.getContext) { return null; @@ -8365,11 +8376,11 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp if (fabric.isLikelyNode) { fabric.StaticCanvas.prototype.createPNGStream = function() { - var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl); + var impl = getNodeCanvas(this.lowerCanvasEl); return impl && impl.createPNGStream(); }; fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { - var impl = fabric.util.getNodeCanvas(this.lowerCanvasEl); + var impl = getNodeCanvas(this.lowerCanvasEl); return impl && impl.createJPEGStream(opts); }; } @@ -10373,15 +10384,20 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab }, /** + * Checks point is inside the object. + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @param {fabric.Object} obj Object to test against + * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target. + * @return {Boolean} true if point is contained within an area of given object * @private */ - _checkTarget: function(pointer, obj) { + _checkTarget: function(pointer, obj, globalPointer) { if (obj && obj.visible && obj.evented && this.containsPoint(null, obj, pointer)){ if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { - var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); + var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y); if (!isTransparent) { return true; } @@ -10393,20 +10409,26 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab }, /** + * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted + * @param {Array} [objects] objects array to look into + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @return {fabric.Object} object that contains pointer * @private */ _searchPossibleTargets: function(objects, pointer) { - // Cache all targets where their bounding box contains point. - var target, i = objects.length, normalizedPointer, subTarget; + var target, i = objects.length, subTarget; // Do not check for currently grouped objects, since we check the parent group itself. // until we call this function specifically to search inside the activeGroup while (i--) { - if (this._checkTarget(pointer, objects[i])) { + var objToCheck = objects[i]; + if (this._checkTarget(objToCheck.group && objToCheck.group.type !== 'activeSelection' + ? this._normalizePointer(objToCheck.group, pointer) + : pointer, + objToCheck, pointer)) { target = objects[i]; if (target.subTargetCheck && target instanceof fabric.Group) { - normalizedPointer = this._normalizePointer(target, pointer); - subTarget = this._searchPossibleTargets(target._objects, normalizedPointer); + subTarget = this._searchPossibleTargets(target._objects, pointer); subTarget && this.targets.push(subTarget); } break; @@ -12135,11 +12157,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab __toDataURL: function(format, quality) { var canvasEl = this.contextContainer.canvas; - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (format === 'jpg') { - format = 'jpeg'; - } - var data = supportQuality ? canvasEl.toDataURL('image/' + format, quality) : canvasEl.toDataURL('image/' + format); @@ -13906,7 +13923,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * Creates an instance of fabric.Image out of an object * @param {Function} callback callback, invoked with an instance as a first argument * @param {Object} [options] for clone as image, passed to toDataURL - * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 * @return {fabric.Object} thisArg */ cloneAsImage: function(callback, options) { @@ -13931,42 +13957,52 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format */ toDataURL: function(options) { options || (options = { }); - var origParams = fabric.util.saveObjectTransform(this); + var utils = fabric.util, origParams = utils.saveObjectTransform(this), + originalShadow = this.shadow, abs = Math.abs; if (options.withoutTransform) { - fabric.util.resetObjectTransform(this); + utils.resetObjectTransform(this); + } + if (options.withoutShadow) { + this.shadow = null; } var el = fabric.util.createCanvasElement(), // skip canvas zoom and calculate with setCoords now. - boundingRect = this.getBoundingRect(true, true); - - el.width = boundingRect.width; - el.height = boundingRect.height; + boundingRect = this.getBoundingRect(true, true), + shadow = this.shadow, scaling, + shadowOffset = { x: 0, y: 0 }, shadowBlur; + + if (shadow) { + shadowBlur = shadow.blur; + scaling = this.getObjectScaling(); + shadowOffset.x = 2 * Math.round((abs(shadow.offsetX) + shadowBlur) * abs(scaling.scaleX)); + shadowOffset.y = 2 * Math.round((abs(shadow.offsetY) + shadowBlur) * abs(scaling.scaleY)); + } + el.width = boundingRect.width + shadowOffset.x; + el.height = boundingRect.height + shadowOffset.y; + el.width += el.width % 2 ? 2 - el.width % 2 : 0; + el.height += el.height % 2 ? 2 - el.height % 2 : 0; var canvas = new fabric.StaticCanvas(el, { enableRetinaScaling: options.enableRetinaScaling, renderOnAddRemove: false, skipOffscreen: false, }); - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (options.format === 'jpg') { - options.format = 'jpeg'; - } - if (options.format === 'jpeg') { canvas.backgroundColor = '#fff'; } - this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center'); var originalCanvas = this.canvas; canvas.add(this); var data = canvas.toDataURL(options); + this.shadow = originalShadow; this.set(origParams).setCoords(); this.canvas = originalCanvas; // canvas.dispose will call image.dispose that will nullify the elements @@ -15305,7 +15341,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ getSvgStyles: function(skipShadow) { - var fillRule = this.fillRule, + var fillRule = this.fillRule ? this.fillRule : 'nonzero', strokeWidth = this.strokeWidth ? this.strokeWidth : '0', strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', @@ -15404,45 +15440,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * Returns transform-string for svg-export + * @param {Boolean} use the full transform or the single object one. * @return {String} */ - getSvgTransform: function() { - var angle = this.angle, - skewX = (this.skewX % 360), - skewY = (this.skewY % 360), - center = this.getCenterPoint(), - - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - translatePart = 'translate(' + - toFixed(center.x, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(center.y, NUM_FRACTION_DIGITS) + - ')', - - anglePart = angle !== 0 - ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') - : '', - - scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (' scale(' + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ')'), - - skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '', - - skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '', - - flipXPart = this.flipX ? ' matrix(-1 0 0 1 0 0) ' : '', - - flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 0)' : ''; - - return [ - translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart - ].join(''); + getSvgTransform: function(full, additionalTransform) { + var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(), + svgTransform = transform.map(function(value) { + return toFixed(value, fabric.Object.NUM_FRACTION_DIGITS); + }).join(' '); + return 'transform="matrix(' + svgTransform + ')' + + (additionalTransform || '') + this.getSvgTransformMatrix() + '" '; }, /** @@ -15450,7 +15457,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} */ getSvgTransformMatrix: function() { - return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; + return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; }, _setSVGBg: function(textBgRects) { @@ -15471,12 +15478,77 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } }, + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver }); + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(), { reviver: reviver }); + }, + /** * @private */ - _createBaseSVGMarkup: function() { - var markup = [], clipPath = this.clipPath; + _createBaseClipPathSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var reviver = options.reviver, + additionalTransform = options.additionalTransform || '', + commonPieces = [ + this.getSvgTransform(true, additionalTransform), + this.getSvgCommons(), + ].join(''), + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'); + objectMarkup[index] = commonPieces; + return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join(''); + }, + /** + * @private + */ + _createBaseSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var noStyle = options.noStyle, withShadow = options.withShadow, + reviver = options.reviver, + styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ', + shadowInfo = withShadow ? 'style="' + this.getSvgFilter() + '" ' : '', + clipPath = this.clipPath, + absoluteClipPath = this.clipPath && this.clipPath.absolutePositioned, + commonPieces, markup = [], clipPathMarkup, + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'); + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + clipPathMarkup = '\n' + + this.clipPath.toClipPathSVG(reviver) + + '\n'; + } + if (absoluteClipPath) { + markup.push( + '\n' + ); + } + markup.push( + '\n' + ); + commonPieces = [ + styleInfo, + noStyle ? '' : this.addPaintOrder(), ' ' + ].join(''); + objectMarkup[index] = commonPieces; if (this.fill && this.fill.toLive) { markup.push(this.fill.toSVG(this, false)); } @@ -15487,14 +15559,12 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot markup.push(this.shadow.toSVG(this)); } if (clipPath) { - clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; - markup.push( - '\n\t', - this.clipPath.toSVG(), - '\n' - ); + markup.push(clipPathMarkup); } - return markup; + markup.push(objectMarkup.join('')); + markup.push('\n'); + absoluteClipPath && markup.push('\n'); + return reviver ? reviver(markup.join('')) : markup.join(''); }, addPaintOrder: function() { @@ -16502,26 +16572,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - p = this.calcLinePoints(); - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ }); @@ -16681,26 +16745,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot }, /* _TO_SVG_START_ */ + /** * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0, + _toSVG: function() { + var svgString, x = 0, y = 0, angle = (this.endAngle - this.startAngle) % ( 2 * pi); if (angle === 0) { - markup.push( - '\n' - ); + '" />\n' + ]; } else { var startX = fabric.util.cos(this.startAngle) * this.radius, @@ -16708,20 +16769,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot endX = fabric.util.cos(this.endAngle) * this.radius, endY = fabric.util.sin(this.endAngle) * this.radius, largeFlag = angle > pi ? '1' : '0'; - - markup.push( + svgString = [ '\n' - ); + '"', 'COMMON_PARTS', ' />\n' + ]; } - - return reviver ? reviver(markup.join('')) : markup.join(''); + return svgString; }, /* _TO_SVG_END_ */ @@ -16894,31 +16949,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - widthBy2 = this.width / 2, + _toSVG: function() { + var widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + ' ' + heightBy2, '0 ' + -heightBy2, widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />' + ]; }, /* _TO_SVG_END_ */ }); @@ -17044,24 +17091,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ @@ -17273,23 +17313,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2; - markup.push( - '\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ }); @@ -17460,12 +17495,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { + _toSVG: function() { var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y, - markup = this._createBaseSVGMarkup(), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; for (var i = 0, len = this.points.length; i < len; i++) { @@ -17474,17 +17508,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ' ); } - markup.push( - '<', this.type, ' ', this.getSvgCommons(), + return [ + '<' + this.type + ' ', 'COMMON_PARTS', 'points="', points.join(''), - '" style="', this.getSvgStyles(), - '" transform="', this.getSvgTransform(), - ' ', this.getSvgTransformMatrix(), '"', - this.addPaintOrder(), - '/>\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ @@ -17695,6 +17723,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, + toFixed = fabric.util.toFixed, commandLengths = { m: 2, l: 2, @@ -18153,29 +18182,39 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var specificTransform = this._getOffsetTransform(), + path = this.path.map(function(path) { + return path.join(' '); + }).join(' '); + return [ + '\n' + ]; + }, + + _getOffsetTransform: function() { + var digits = fabric.Object.NUM_FRACTION_DIGITS; + return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' + + toFixed(-this.pathOffset.y, digits) + ')'; + }, + + /** + * Returns svg clipPath representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ - toSVG: function(reviver) { - var chunks = [], - markup = this._createBaseSVGMarkup(), addTransform = ''; - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; - markup.push( - '\n' + toClipPathSVG: function(reviver) { + var additionalTransform = this._getOffsetTransform(); + return '\t' + this._createBaseClipPathSVGMarkup( + this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform } ); - - return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ @@ -19132,24 +19171,30 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - markup.push( - '\n' - ); + var svgString = []; for (var i = 0, len = this._objects.length; i < len; i++) { - markup.push('\t', this._objects[i].toSVG(reviver)); + svgString.push('\t', this._objects[i].toSVG(reviver)); } - markup.push('\n'); + return this._createBaseSVGMarkup( + svgString, + { reviver: reviver, noStyle: true, withShadow: true }); + }, - return reviver ? reviver(markup.join('')) : markup.join(''); + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + var svgString = []; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t', this._objects[i].toClipPathSVG(reviver)); + } + + return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver }); }, /* _TO_SVG_END_ */ }); @@ -19620,53 +19665,51 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /* _TO_SVG_START_ */ /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2, clipPath = ''; + _toSVG: function() { + var svgString = [], imageMarkup = [], strokeSvg, + x = -this.width / 2, y = -this.height / 2, clipPath = ''; if (this.hasCrop()) { var clipPathId = fabric.Object.__uid++; - markup.push( + svgString.push( '\n', '\t\n', '\n' ); clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; } - markup.push('\n'); - var imageMarkup = ['\t\n']; - if (this.paintFirst === 'fill') { - Array.prototype.push.apply(markup, imageMarkup); - } + '>\n'); + if (this.stroke || this.strokeDashArray) { var origFill = this.fill; this.fill = null; - markup.push( + strokeSvg = [ '\t\n' - ); + ]; this.fill = origFill; } if (this.paintFirst !== 'fill') { - Array.prototype.push.apply(markup, imageMarkup); + svgString = svgString.concat(strokeSvg, imageMarkup); } - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); + else { + svgString = svgString.concat(imageMarkup, strokeSvg); + } + return svgString; }, /* _TO_SVG_END_ */ @@ -19819,6 +19862,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderPaintInOrder(ctx); }, + /** + * Decide if the object should cache or not. Create its own cache level + * objectCaching is a global flag, wins over everything + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * This is the special image version where we would like to avoid caching where possible. + * Essentially images do not benefit from caching. They may require caching, and in that + * case we do it. Also caching an image usually ends in a loss of details. + * A full performance audit should be done. + * @return {Boolean} + */ + shouldCache: function() { + this.ownCaching = this.objectCaching && this.needsItsOwnCache(); + return this.ownCaching; + }, + _renderFill: function(ctx) { var w = this.width, h = this.height, sW = w * this._filterScalingX, sH = h * this._filterScalingY, x = -w / 2, y = -h / 2, elementToDraw = this._element; @@ -22961,8 +23021,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) { /** * Resize type - * for webgl resizyType is just lanczos, for canvas2d can be: - * bilinear, hermite, sliceHacl, lanczos. + * for webgl resizeType is just lanczos, for canvas2d can be: + * bilinear, hermite, sliceHack, lanczos. * @param {String} resizeType * @default */ @@ -28296,12 +28356,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - offsets = this._getSVGLeftTopOffsets(), - textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - this._wrapSVGTextAndBg(markup, textAndBg); - - return reviver ? reviver(markup.join('')) : markup.join(''); + var offsets = this._getSVGLeftTopOffsets(), + textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft), + internalMarkup = this._wrapSVGTextAndBg(textAndBg); + return this._createBaseSVGMarkup( + internalMarkup, { reviver: reviver, noStyle: true, withShadow: true }); }, /** @@ -28318,13 +28377,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot /** * @private */ - _wrapSVGTextAndBg: function(markup, textAndBg) { - var noShadow = true, filter = this.getSvgFilter(), - style = filter === '' ? '' : ' style="' + filter + '"', + _wrapSVGTextAndBg: function(textAndBg) { + var noShadow = true, textDecoration = this.getSvgTextDecoration(this); - markup.push( - '\t\n', + return [ textAndBg.textBgRects.join(''), '\t\t', textAndBg.textSpans.join(''), - '\n', - '\t\n' - ); + '\n' + ]; }, /** diff --git a/package.json b/package.json index 93afdb04a3e..7b0d03c2b1a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "fabric", "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", "homepage": "http://fabricjs.com/", - "version": "2.4.1", + "version": "2.4.2", "author": "Juriy Zaytsev ", "contributors": [ { diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index d2d637c2dee..adc2b752607 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -872,7 +872,7 @@ canvasClip.clipPath = new fabric.Rect({ width: 200, height: 200 }); canvasClip.add(new fabric.Circle({ radius: 200 })); var svg = canvasClip.toSVG(); - var expectedSVG = '\n\n\nCreated with Fabric.js 2.4.1\n\n\n\t\n\n\n\n\n\n\n\n'; + var expectedSVG = '\n\n\nCreated with Fabric.js ' + fabric.version + '\n\n\n\t\n\n\n\n\n\n\n\n'; assert.equal(svg, expectedSVG, 'SVG with clipPath should match'); }); @@ -884,7 +884,7 @@ canvas.renderOnAddRemove = false; canvas.backgroundImage = imageBG; canvas.overlayImage = imageOL; - var expectedSVG = '\n\n\nCreated with Fabric.js 2.4.1\n\n\n\n\t\n\n\n\t\n\n'; + var expectedSVG = '\n\n\nCreated with Fabric.js ' + fabric.version + '\n\n\n\n\t\n\n\n\t\n\n'; var svg1 = canvas.toSVG(); assert.equal(svg1, expectedSVG, 'svg with bg and overlay do not match'); imageBG.excludeFromExport = true;