diff --git a/CHANGELOG.md b/CHANGELOG.md index 7068d957..a14a5d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.6.0] +- Fix: avoid ie11 to throw on weird draw images [#5428](https://github.com/fabricjs/fabric.js/pull/5428) +- Fix: a rare case of invisible clipPath [#5477](https://github.com/fabricjs/fabric.js/pull/5477) +- Fix: testability of code under node when webgl is involved [#5478](https://github.com/fabricjs/fabric.js/pull/5478) +- Add: Grapeheme text wrapping for Textbox (Textbox.splitByGrapheme) [#5479](https://github.com/fabricjs/fabric.js/pull/5479) +- Add: fabric.Object.toCanvasElement [#5481](https://github.com/fabricjs/fabric.js/pull/5481) + ## [2.5.0] - Fix: textbox transform report newScaleX and newScaleY values [#5464](https://github.com/fabricjs/fabric.js/pull/5464) - Fix: export of svg and gradient with transforms [#5456](https://github.com/fabricjs/fabric.js/pull/5456) diff --git a/HEADER.js b/HEADER.js index 181ae26e..d447dd87 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.5.0' }; +var fabric = fabric || { version: '2.6.0' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } diff --git a/dist/fabric.js b/dist/fabric.js index a63c005e..85fc9bd8 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.5.0' }; +var fabric = fabric || { version: '2.6.0' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -1174,6 +1174,7 @@ fabric.CommonMethods = { /** * Creates a canvas element that is a copy of another and is also painted + * @param {CanvasElement} canvas to copy size and content of * @static * @memberOf fabric.util * @return {CanvasElement} initialized canvas element @@ -1186,6 +1187,19 @@ fabric.CommonMethods = { return newCanvas; }, + /** + * since 2.6.0 moved from canvas instance to utility. + * @param {CanvasElement} canvasEl to copy size and content of + * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too + * @param {Number} quality <= 1 and > 0 + * @static + * @memberOf fabric.util + * @return {String} data url + */ + toDataURL: function(canvasEl, format, quality) { + return canvasEl.toDataURL('image/' + format, quality); + }, + /** * Creates image element (works on client and node) * @static @@ -8328,7 +8342,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp * (either those of HTMLCanvasElement itself, or rendering context) * * @param {String} methodName Method to check support for; - * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" + * Could be one of "setLineDash" * @return {Boolean | null} `true` if method is supported (or at least exists), * `null` if canvas element or context can not be initialized */ @@ -8346,23 +8360,9 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp switch (methodName) { - case 'getImageData': - return typeof ctx.getImageData !== 'undefined'; - case 'setLineDash': return typeof ctx.setLineDash !== 'undefined'; - case 'toDataURL': - return typeof el.toDataURL !== 'undefined'; - - case 'toDataURLWithQuality': - try { - el.toDataURL('image/jpeg', 0); - return true; - } - catch (e) { } - return false; - default: return null; } @@ -12074,9 +12074,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab (function () { - - var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality'); - fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** @@ -12118,7 +12115,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab quality = options.quality || 1, multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? this.getRetinaScaling() : 1), canvasEl = this.toCanvasElement(multiplier, options); - return this.__toDataURL(canvasEl, format, quality); + return fabric.util.toDataURL(canvasEl, format, quality); }, /** @@ -12168,17 +12165,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.interactive = originalInteractive; return canvasEl; }, - - /** - * since 2.5.0 does not need to be on canvas instance anymore. - * leave it here for context; - * @private - */ - __toDataURL: function(canvasEl, format, quality) { - return supportQuality - ? canvasEl.toDataURL('image/' + format, quality) - : canvasEl.toDataURL('image/' + format); - } }); })(); @@ -13586,8 +13572,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {CanvasRenderingContext2D} ctx Context to render on */ drawObject: function(ctx, forClipping) { - + var originalFill = this.fill, originalStroke = this.stroke; if (forClipping) { + this.fill = 'black'; + this.stroke = ''; this._setClippingProperties(ctx); } else { @@ -13597,6 +13585,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati } this._render(ctx); this._drawClipPath(ctx); + this.fill = originalFill; + this.stroke = originalStroke; }, _drawClipPath: function(ctx) { @@ -13951,6 +13941,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati /** * Creates an instance of fabric.Image out of an object + * could make use of both toDataUrl or toCanvasElement. * @param {Function} callback callback, invoked with an instance as a first argument * @param {Object} [options] for clone as image, passed to toDataURL * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" @@ -13966,20 +13957,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {fabric.Object} thisArg */ cloneAsImage: function(callback, options) { - var dataUrl = this.toDataURL(options); - fabric.util.loadImage(dataUrl, function(img) { - if (callback) { - callback(new fabric.Image(img)); - } - }); + var canvasEl = this.toCanvasElement(options); + if (callback) { + callback(new fabric.Image(canvasEl)); + } return this; }, /** - * Converts an object into a data-url-like string + * Converts an object into a HTMLCanvas element * @param {Object} options Options object - * @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 @@ -13990,11 +13977,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @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) { + toCanvasElement: function(options) { options || (options = { }); var utils = fabric.util, origParams = utils.saveObjectTransform(this), - originalShadow = this.shadow, abs = Math.abs; + originalShadow = this.shadow, abs = Math.abs, + multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? fabric.devicePixelRatio : 1); if (options.withoutTransform) { utils.resetObjectTransform(this); @@ -14020,7 +14008,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati 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, + enableRetinaScaling: false, renderOnAddRemove: false, skipOffscreen: false, }); @@ -14031,10 +14019,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var originalCanvas = this.canvas; canvas.add(this); - var data = canvas.toDataURL(options); + var canvasEl = canvas.toCanvasElement(multiplier || 1, options); this.shadow = originalShadow; - this.set(origParams).setCoords(); this.canvas = originalCanvas; + this.set(origParams).setCoords(); // canvas.dispose will call image.dispose that will nullify the elements // since this canvas is a simple element for the process, we remove references // to objects in this way in order to avoid object trashing. @@ -14042,7 +14030,27 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati canvas.dispose(); canvas = null; - return data; + return canvasEl; + }, + + /** + * Converts an object into a data-url-like string + * @param {Object} options Options object + * @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 {String} Returns a data: URL containing a representation of the object in the format specified by options.format + */ + toDataURL: function(options) { + options || (options = { }); + return fabric.util.toDataURL(this.toCanvasElement(options), options.format || 'png', options.quality || 1); }, /** @@ -19931,14 +19939,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot }, _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; - elementToDraw && ctx.drawImage(elementToDraw, - this.cropX * this._filterScalingX, - this.cropY * this._filterScalingY, - sW, - sH, - x, y, w, h); + var elementToDraw = this._element, + w = this.width, h = this.height, + sW = Math.min(elementToDraw.naturalWidth || elementToDraw.width, w * this._filterScalingX), + sH = Math.min(elementToDraw.naturalHeight || elementToDraw.height, h * this._filterScalingY), + x = -w / 2, y = -h / 2, + sX = Math.max(0, this.cropX * this._filterScalingX), + sY = Math.max(0, this.cropY * this._filterScalingY); + + elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, w, h); }, /** @@ -20465,51 +20474,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return pipelineState; }, - /** - * The same as the applyFilter method but with additional logging of WebGL - * errors. - */ - applyFiltersDebug: function(filters, source, width, height, targetCanvas, cacheKey) { - // The following code is useful when debugging a specific issue but adds ~10x slowdown. - var gl = this.gl; - var ret = this.applyFilters(filters, source, width, height, targetCanvas, cacheKey); - var glError = gl.getError(); - if (glError !== gl.NO_ERROR) { - var errorString = this.glErrorToString(gl, glError); - var error = new Error('WebGL Error ' + errorString); - error.glErrorCode = glError; - throw error; - } - return ret; - }, - - glErrorToString: function(context, errorCode) { - if (!context) { - return 'Context undefined for error code: ' + errorCode; - } - else if (typeof errorCode !== 'number') { - return 'Error code is not a number'; - } - switch (errorCode) { - case context.NO_ERROR: - return 'NO_ERROR'; - case context.INVALID_ENUM: - return 'INVALID_ENUM'; - case context.INVALID_VALUE: - return 'INVALID_VALUE'; - case context.INVALID_OPERATION: - return 'INVALID_OPERATION'; - case context.INVALID_FRAMEBUFFER_OPERATION: - return 'INVALID_FRAMEBUFFER_OPERATION'; - case context.OUT_OF_MEMORY: - return 'OUT_OF_MEMORY'; - case context.CONTEXT_LOST_WEBGL: - return 'CONTEXT_LOST_WEBGL'; - default: - return 'UNKNOWN_ERROR'; - } - }, - /** * Detach event listeners, remove references, and clean up caches. */ @@ -20603,9 +20567,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati if (this.gpuInfo) { return this.gpuInfo; } - var gl = this.gl; + var gl = this.gl, gpuInfo = { renderer: '', vendor: '' }; + if (!gl) { + return gpuInfo; + } var ext = gl.getExtension('WEBGL_debug_renderer_info'); - var gpuInfo = { renderer: '', vendor: '' }; if (ext) { var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); var vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); @@ -28693,6 +28659,20 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat('width'), + /** + * Use this regular expression to split strings in breakable lines + * @private + */ + _wordJoiners: /[ \t\r\u200B\u200C]/, + + /** + * Use this boolean property in order to split strings that have no white space concept. + * this is a cheap way to help with chinese/japaense + * @type Boolean + * @since 2.6.0 + */ + splitByGrapheme: false, + /** * Unlike superclass's version of this function, Textbox does not update * its width. @@ -28929,19 +28909,20 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * to. */ _wrapLine: function(_line, lineIndex, desiredWidth, reservedSpace) { - var lineWidth = 0, - graphemeLines = [], - line = [], + var lineWidth = 0, + splitByGrapheme = this.splitByGrapheme, + graphemeLines = [], + line = [], // spaces in different languges? - words = _line.split(this._reSpaceAndTab), - word = '', - offset = 0, - infix = ' ', - wordWidth = 0, - infixWidth = 0, + words = splitByGrapheme ? fabric.util.string.graphemeSplit(_line) : _line.split(this._wordJoiners), + word = '', + offset = 0, + infix = splitByGrapheme ? '' : ' ', + wordWidth = 0, + infixWidth = 0, largestWordWidth = 0, lineJustStarted = true, - additionalSpace = this._getWidthOfCharSpacing(), + additionalSpace = splitByGrapheme ? 0 : this._getWidthOfCharSpacing(), reservedSpace = reservedSpace || 0; desiredWidth -= reservedSpace; @@ -29035,7 +29016,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { - return this.callSuper('toObject', ['minWidth'].concat(propertiesToInclude)); + return this.callSuper('toObject', ['minWidth', 'splitByGrapheme'].concat(propertiesToInclude)); } }); diff --git a/package.json b/package.json index 8ec36b8b..390f8e5a 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.5.0", + "version": "2.6.0", "authors": "Juriy Zaytsev ", "contributors": [ {