This commit is contained in:
Andrea Bogazzi 2019-02-17 00:21:56 +01:00
commit 0a83d45b70
17 changed files with 334 additions and 279 deletions

View file

@ -85,14 +85,14 @@ jobs:
script: npm run build:fast && npm run test:visual
- stage: Visual Tests
env: LAUNCHER=Chrome
install: npm install testem@1.18.4 qunit@2.6.1
install: npm install testem@1.18.4 qunit@2.6.2
script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
addons:
apt:
packages: # avoid installing packages
- stage: Visual Tests
env: LAUNCHER=Firefox
install: npm install testem@1.18.4 qunit@2.6.1
install: npm install testem@1.18.4 qunit@2.6.2
script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
addons:
apt:

View file

@ -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)

View file

@ -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;
}

View file

@ -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 <kangax@gmail.com>",
"contributors": [
{
@ -68,7 +68,7 @@
"eslint": "4.18.x",
"istanbul": "0.4.x",
"onchange": "^3.x.x",
"qunit": "^2.6.1",
"qunit": "2.6.2",
"testem": "^1.18.4",
"uglify-js": "3.3.x",
"pixelmatch": "^4.0.2",

View file

@ -219,51 +219,6 @@
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.
*/
@ -357,9 +312,11 @@
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);

View file

@ -1,7 +1,4 @@
(function () {
var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality');
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
/**
@ -43,7 +40,7 @@
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);
},
/**
@ -93,17 +90,6 @@
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);
}
});
})();

View file

@ -45,6 +45,15 @@
*/
strokeWidth: 0,
/**
* When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`.
* This allows for relative urls as image src.
* @since 2.7.0
* @type Boolean
* @default
*/
srcFromAttribute: false,
/**
* private
* contains last value of scaleX to detect
@ -93,7 +102,7 @@
/**
* key used to retrieve the texture representing this image
* since 2.0.0
* @since 2.0.0
* @type String
* @default
*/
@ -101,7 +110,7 @@
/**
* Image crop in pixels from original image size.
* since 2.0.0
* @since 2.0.0
* @type Number
* @default
*/
@ -109,7 +118,7 @@
/**
* Image crop in pixels from original image size.
* since 2.0.0
* @since 2.0.0
* @type Number
* @default
*/
@ -349,7 +358,13 @@
if (element.toDataURL) {
return element.toDataURL();
}
return element.src;
if (this.srcFromAttribute) {
return element.getAttribute('src');
}
else {
return element.src;
}
}
else {
return this.src || '';
@ -505,14 +520,15 @@
},
_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);
},
/**

View file

@ -1187,8 +1187,10 @@
* @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 {
@ -1198,6 +1200,8 @@
}
this._render(ctx);
this._drawClipPath(ctx);
this.fill = originalFill;
this.stroke = originalStroke;
},
_drawClipPath: function(ctx) {
@ -1558,6 +1562,7 @@
/**
* 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"
@ -1573,20 +1578,16 @@
* @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
@ -1597,11 +1598,12 @@
* @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);
@ -1627,7 +1629,7 @@
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,
});
@ -1638,10 +1640,10 @@
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.
@ -1649,7 +1651,27 @@
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);
},
/**

View file

@ -64,6 +64,20 @@
*/
_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.
@ -300,19 +314,20 @@
* 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;
@ -406,7 +421,7 @@
* @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));
}
});

View file

@ -1795,7 +1795,7 @@
* (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
*/
@ -1813,23 +1813,9 @@
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;
}

View file

@ -583,6 +583,7 @@
/**
* 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
@ -595,6 +596,19 @@
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

View file

@ -1170,16 +1170,11 @@
QUnit.test('toDataURL', function(assert) {
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
}
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
});
// QUnit.test('getPointer', function(assert) {

View file

@ -482,20 +482,15 @@
QUnit.test('toDataURL', function(assert) {
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var rect = new fabric.Rect({width: 100, height: 100, fill: 'red', top: 0, left: 0});
canvas.add(rect);
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
//we can just compare that the dataUrl generated differs from the dataURl of an empty canvas.
assert.equal(dataURL.substring(200, 210) !== 'AAAAAAAAAA', true);
}
var rect = new fabric.Rect({width: 100, height: 100, fill: 'red', top: 0, left: 0});
canvas.add(rect);
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
//we can just compare that the dataUrl generated differs from the dataURl of an empty canvas.
assert.equal(dataURL.substring(200, 210) !== 'AAAAAAAAAA', true);
});
QUnit.test('toDataURL with enableRetinaScaling: true and no multiplier', function(assert) {
@ -604,39 +599,28 @@
});
QUnit.test('toDataURL jpeg', function(assert) {
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
try {
var dataURL = canvas.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
else {
try {
var dataURL = canvas.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
// node-canvas does not support jpeg data urls
catch (err) {
assert.ok(true);
}
// node-canvas does not support jpeg data urls
catch (err) {
assert.ok(true);
}
});
QUnit.test('toDataURL cropping', function(assert) {
var done = assert.async();
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
done();
}
else {
var croppingWidth = 75,
croppingHeight = 50,
dataURL = canvas.toDataURL({width: croppingWidth, height: croppingHeight});
var croppingWidth = 75,
croppingHeight = 50,
dataURL = canvas.toDataURL({width: croppingWidth, height: croppingHeight});
fabric.Image.fromURL(dataURL, function (img) {
assert.equal(img.width, croppingWidth, 'Width of exported image should correspond to cropping width');
assert.equal(img.height, croppingHeight, 'Height of exported image should correspond to cropping height');
done();
});
}
fabric.Image.fromURL(dataURL, function (img) {
assert.equal(img.width, croppingWidth, 'Width of exported image should correspond to cropping width');
assert.equal(img.height, croppingHeight, 'Height of exported image should correspond to cropping height');
done();
});
});
QUnit.test('centerObjectH', function(assert) {

View file

@ -25,57 +25,59 @@
}
var IMG_SRC = fabric.isLikelyNode ? (__dirname + '/../fixtures/test_image.gif') : getAbsolutePath('../fixtures/test_image.gif'),
IMG_SRC_REL = fabric.isLikelyNode ? (__dirname + '/../fixtures/test_image.gif') : '/fixtures/test_image.gif',
IMG_WIDTH = 276,
IMG_HEIGHT = 110;
var REFERENCE_IMG_OBJECT = {
'version': fabric.version,
'type': 'image',
'originX': 'left',
'originY': 'top',
'left': 0,
'top': 0,
'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects
'height': IMG_HEIGHT, // or does it now?
'fill': 'rgb(0,0,0)',
'stroke': null,
'strokeWidth': 0,
'strokeDashArray': null,
'strokeLineCap': 'butt',
'strokeDashOffset': 0,
'strokeLineJoin': 'miter',
'strokeMiterLimit': 4,
'scaleX': 1,
'scaleY': 1,
'angle': 0,
'flipX': false,
'flipY': false,
'opacity': 1,
'src': IMG_SRC,
'shadow': null,
'visible': true,
'backgroundColor': '',
'clipTo': null,
'filters': [],
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
'transformMatrix': null,
'crossOrigin': '',
'cropX': 0,
'cropY': 0
version: fabric.version,
type: 'image',
originX: 'left',
originY: 'top',
left: 0,
top: 0,
width: IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects
height: IMG_HEIGHT, // or does it now?
fill: 'rgb(0,0,0)',
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: 'butt',
strokeDashOffset: 0,
strokeLineJoin: 'miter',
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
src: IMG_SRC,
shadow: null,
visible: true,
backgroundColor: '',
clipTo: null,
filters: [],
fillRule: 'nonzero',
paintFirst: 'fill',
globalCompositeOperation: 'source-over',
skewX: 0,
skewY: 0,
transformMatrix: null,
crossOrigin: '',
cropX: 0,
cropY: 0
};
function _createImageElement() {
return fabric.document.createElement('img');
}
function _createImageObject(width, height, callback, options) {
function _createImageObject(width, height, callback, options, src) {
options = options || {};
src = src || IMG_SRC;
var elImage = _createImageElement();
setSrc(elImage, IMG_SRC, function() {
setSrc(elImage, src, function() {
options.width = width;
options.height = height;
callback(new fabric.Image(elImage, options));
@ -90,6 +92,10 @@
return _createImageObject(IMG_WIDTH / 2, IMG_HEIGHT / 2, callback, options);
}
function createImageObjectWithSrc(callback, options, src) {
return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback, options, src);
}
function setSrc(img, src, callback) {
img.onload = function() {
callback && callback();
@ -294,6 +300,19 @@
});
});
QUnit.test('getSrc with srcFromAttribute', function(assert) {
var done = assert.async();
createImageObjectWithSrc(function(image) {
assert.equal(image.getSrc(), IMG_SRC_REL);
done();
},
{
srcFromAttribute: true
},
IMG_SRC_REL
);
});
QUnit.test('getElement', function(assert) {
var elImage = _createImageElement();
var image = new fabric.Image(elImage);
@ -780,4 +799,26 @@
done();
});
});
QUnit.test('_renderFill respects source boundaries ', function (assert) {
fabric.Image.prototype._renderFill.call({
cropX: -1,
cropY: -1,
_filterScalingX: 1,
_filterScalingY: 1,
width: 300,
height: 300,
_element: {
naturalWidth: 200,
height: 200,
},
}, {
drawImage: function(src, sX, sY, sW, sH) {
assert.ok(sX >= 0, 'sX should be positive');
assert.ok(sY >= 0, 'sY should be positive');
assert.ok(sW <= 200, 'sW should not be larger than image width');
assert.ok(sH <= 200, 'sH should not be larger than image height');
}
});
});
})();

View file

@ -410,74 +410,71 @@
QUnit.test('cloneAsImage', function(assert) {
var done = assert.async();
var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red', strokeWidth: 0 });
assert.ok(typeof cObj.cloneAsImage === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
fabric.log('`toDataURL` is not supported by this environment; skipping `cloneAsImage` test (as it relies on `toDataURL`)');
cObj.cloneAsImage(function(image) {
assert.ok(image);
assert.ok(image instanceof fabric.Image);
assert.equal(image.width, 100, 'the image has same dimension of object');
done();
}
else {
cObj.cloneAsImage(function(image) {
assert.ok(image);
assert.ok(image instanceof fabric.Image);
assert.equal(image.width, 100, 'the image has same dimension of object');
done();
});
}
});
});
QUnit.test('cloneAsImage with retina scaling enabled', function(assert) {
var done = assert.async();
var cObj = new fabric.Rect({ width: 100, height: 100, fill: 'red', strokeWidth: 0 });
fabric.devicePixelRatio = 2;
if (!fabric.Canvas.supports('toDataURL')) {
fabric.log('`toDataURL` is not supported by this environment; skipping `cloneAsImage` test (as it relies on `toDataURL`)');
cObj.cloneAsImage(function(image) {
assert.ok(image);
assert.ok(image instanceof fabric.Image);
assert.equal(image.width, 200, 'the image has been scaled by retina');
fabric.devicePixelRatio = 1;
done();
}
else {
cObj.cloneAsImage(function(image) {
assert.ok(image);
assert.ok(image instanceof fabric.Image);
assert.equal(image.width, 200, 'the image has been scaled by retina');
fabric.devicePixelRatio = 1;
done();
}, { enableRetinaScaling: true });
}
}, { enableRetinaScaling: true });
});
QUnit.test('toDataURL', function(assert) {
// var data =
// 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQA'+
// 'AABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2s'+
// 'gIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AI'+
// 'akzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR'+
// '6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEm'+
// 'bB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0'+
// 'uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPw'+
// 'JC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC';
QUnit.test('toCanvasElement', function(assert) {
var cObj = new fabric.Rect({
width: 100, height: 100, fill: 'red', strokeWidth: 0
});
assert.ok(typeof cObj.toCanvasElement === 'function');
var canvasEl = cObj.toCanvasElement();
assert.ok(typeof canvasEl.getContext === 'function', 'the element returned is a canvas');
});
QUnit.test('toCanvasElement does not modify oCoords on zoomed canvas', function(assert) {
var cObj = new fabric.Rect({
width: 100, height: 100, fill: 'red', strokeWidth: 0
});
canvas.setZoom(2);
canvas.add(cObj);
var originaloCoords = cObj.oCoords;
var originalaCoords = cObj.aCoords;
cObj.toCanvasElement();
assert.deepEqual(cObj.oCoords, originaloCoords, 'cObj did not get object coords changed');
assert.deepEqual(cObj.aCoords, originalaCoords, 'cObj did not get absolute coords changed');
});
QUnit.test('toDataURL', function(assert) {
var cObj = new fabric.Rect({
width: 100, height: 100, fill: 'red', strokeWidth: 0
});
assert.ok(typeof cObj.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var dataURL = cObj.toDataURL();
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
var dataURL = cObj.toDataURL();
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
try {
dataURL = cObj.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
catch (err) {
fabric.log('jpeg toDataURL not supported');
}
try {
dataURL = cObj.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
catch (err) {
fabric.log('jpeg toDataURL not supported');
}
});
@ -496,16 +493,10 @@
width: 100, height: 100, fill: 'red'
});
canvas.add(cObj);
var objCanvas = cObj.canvas;
cObj.toDataURL();
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var objCanvas = cObj.canvas;
cObj.toDataURL();
assert.equal(objCanvas, cObj.canvas);
}
assert.equal(objCanvas, cObj.canvas);
});
QUnit.test('isType', function(assert) {

View file

@ -31,17 +31,17 @@
opacity: 1,
shadow: null,
visible: true,
clipTo: null,
text: 'x',
fontSize: 40,
fontWeight: 'normal',
fontFamily: 'Times New Roman',
fontStyle: 'normal',
lineHeight: 1.16,
underline: false,
overline: false,
linethrough: false,
textAlign: 'left',
clipTo: null,
text: 'x',
fontSize: 40,
fontWeight: 'normal',
fontFamily: 'Times New Roman',
fontStyle: 'normal',
lineHeight: 1.16,
underline: false,
overline: false,
linethrough: false,
textAlign: 'left',
backgroundColor: '',
textBackgroundColor: '',
fillRule: 'nonzero',
@ -52,7 +52,8 @@
transformMatrix: null,
charSpacing: 0,
styles: { },
minWidth: 20
minWidth: 20,
splitByGrapheme: false,
};
QUnit.test('constructor', function(assert) {
@ -129,6 +130,30 @@
textbox.initDimensions();
assert.equal(textbox.textLines[0], 'xa', 'first line match expectations spacing 800');
});
QUnit.test('wrapping with different things', function(assert) {
var textbox = new fabric.Textbox('xa\u200Bxb\u200Bxc\u200Cxd\u200Cxe ya yb id', {
width: 16,
});
assert.equal(textbox.textLines[0], 'xa', '0 line match expectations');
assert.equal(textbox.textLines[1], 'xb', '1 line match expectations');
assert.equal(textbox.textLines[2], 'xc', '2 line match expectations');
assert.equal(textbox.textLines[3], 'xd', '3 line match expectations');
assert.equal(textbox.textLines[4], 'xe', '4 line match expectations');
assert.equal(textbox.textLines[5], 'ya', '5 line match expectations');
assert.equal(textbox.textLines[6], 'yb', '6 line match expectations');
});
QUnit.test('wrapping with splitByGrapheme', function(assert) {
var textbox = new fabric.Textbox('xaxbxcxdxeyaybid', {
width: 1,
splitByGrapheme: true,
});
assert.equal(textbox.textLines[0], 'x', '0 line match expectations splitByGrapheme');
assert.equal(textbox.textLines[1], 'a', '1 line match expectations splitByGrapheme');
assert.equal(textbox.textLines[2], 'x', '2 line match expectations splitByGrapheme');
assert.equal(textbox.textLines[3], 'b', '3 line match expectations splitByGrapheme');
assert.equal(textbox.textLines[4], 'x', '4 line match expectations splitByGrapheme');
assert.equal(textbox.textLines[5], 'c', '5 line match expectations splitByGrapheme');
});
QUnit.test('wrapping with custom space', function(assert) {
var textbox = new fabric.Textbox('xa xb xc xd xe ya yb id', {
width: 2000,

View file

@ -48,6 +48,22 @@
percentage: 0.06,
});
function clipping02(canvas, callback) {
var clipPath = new fabric.Circle({ radius: 50, strokeWidth: 40, top: -50, left: -50, fill: '' });
var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'});
obj.clipPath = clipPath;
canvas.add(obj);
canvas.renderAll();
callback(canvas.lowerCanvasEl);
}
tests.push({
test: 'falsy values for fill are handled',
code: clipping02,
golden: 'clipping01.png',
percentage: 0.06,
});
function clipping1(canvas, callback) {
var zoom = 20;
canvas.setZoom(zoom);