From 54125cd2bdf76bc96cc00d9216c69f2b6196150c Mon Sep 17 00:00:00 2001 From: Joris Ooms Date: Sat, 28 Dec 2019 14:31:55 +0100 Subject: [PATCH] Upper canvas retina scaling (#5938) --- src/brushes/base_brush.class.js | 17 +- src/canvas.class.js | 26 +- src/static_canvas.class.js | 14 +- test/unit/brushes.js | 151 ++-- test/unit/itext.js | 1267 ++++++++++++++-------------- test/unit/itext_click_behaviour.js | 532 ++++++------ 6 files changed, 1063 insertions(+), 944 deletions(-) diff --git a/src/brushes/base_brush.class.js b/src/brushes/base_brush.class.js index 568f8c9a12c..8b1c8aa541a 100644 --- a/src/brushes/base_brush.class.js +++ b/src/brushes/base_brush.class.js @@ -103,13 +103,18 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype return; } - var ctx = this.canvas.contextTop, - zoom = this.canvas.getZoom(); + var canvas = this.canvas, + shadow = this.shadow, + ctx = canvas.contextTop, + zoom = canvas.getZoom(); + if (canvas && canvas._isRetinaScaling()) { + zoom *= fabric.devicePixelRatio; + } - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur * zoom; - ctx.shadowOffsetX = this.shadow.offsetX * zoom; - ctx.shadowOffsetY = this.shadow.offsetY * zoom; + ctx.shadowColor = shadow.color; + ctx.shadowBlur = shadow.blur * zoom; + ctx.shadowOffsetX = shadow.offsetX * zoom; + ctx.shadowOffsetY = shadow.offsetY * zoom; }, needsFullRender: function() { diff --git a/src/canvas.class.js b/src/canvas.class.js index eb39c6cb068..128c38ebaea 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -1347,6 +1347,12 @@ pointer = this.restorePointerVpt(pointer); } + var retinaScaling = this.getRetinaScaling(); + if (retinaScaling !== 1) { + pointer.x /= retinaScaling; + pointer.y /= retinaScaling; + } + if (boundsWidth === 0 || boundsHeight === 0) { // If bounds are not available (i.e. not visible), do not apply scale. cssScale = { width: 1, height: 1 }; @@ -1369,22 +1375,24 @@ * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized */ _createUpperCanvas: function () { - var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); + var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''), + lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl; // there is no need to create a new upperCanvas element if we have already one. - if (this.upperCanvasEl) { - this.upperCanvasEl.className = ''; + if (upperCanvasEl) { + upperCanvasEl.className = ''; } else { - this.upperCanvasEl = this._createCanvasElement(); + upperCanvasEl = this._createCanvasElement(); + this.upperCanvasEl = upperCanvasEl; } - fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); + fabric.util.addClass(upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); - this.wrapperEl.appendChild(this.upperCanvasEl); + this.wrapperEl.appendChild(upperCanvasEl); - this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); - this._applyCanvasStyle(this.upperCanvasEl); - this.contextTop = this.upperCanvasEl.getContext('2d'); + this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl); + this._applyCanvasStyle(upperCanvasEl); + this.contextTop = upperCanvasEl.getContext('2d'); }, /** diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index fbb2a789cb8..027db254f33 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -276,12 +276,20 @@ if (!this._isRetinaScaling()) { return; } - this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio); - this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio); + var scaleRatio = fabric.devicePixelRatio; + this.__initRetinaScaling(scaleRatio, this.lowerCanvasEl, this.contextContainer); + if (this.upperCanvasEl) { + this.__initRetinaScaling(scaleRatio, this.upperCanvasEl, this.contextTop); + } + }, - this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio); + __initRetinaScaling: function(scaleRatio, canvas, context) { + canvas.setAttribute('width', this.width * scaleRatio); + canvas.setAttribute('height', this.height * scaleRatio); + context.scale(scaleRatio, scaleRatio); }, + /** * Calculates canvas element offset relative to the document * This method is also attached as "resize" event handler of window diff --git a/test/unit/brushes.js b/test/unit/brushes.js index 70ce2bf750d..878dff2fa89 100644 --- a/test/unit/brushes.js +++ b/test/unit/brushes.js @@ -1,79 +1,88 @@ (function() { var canvas = new fabric.Canvas(); - QUnit.module('fabric.BaseBrush', { - afterEach: function() { + QUnit.module('fabric.BaseBrush', function(hooks) { + hooks.afterEach(function() { canvas.cancelRequestedRender(); - } - }); - QUnit.test('fabric brush constructor', function(assert) { - assert.ok(fabric.BaseBrush); + }); - var brush = new fabric.BaseBrush(); + QUnit.test('fabric brush constructor', function(assert) { + assert.ok(fabric.BaseBrush); - assert.ok(brush instanceof fabric.BaseBrush, 'should inherit from fabric.BaseBrush'); - assert.equal(brush.color, 'rgb(0, 0, 0)', 'default color is black'); - assert.equal(brush.width, 1, 'default width is 1'); - assert.ok(typeof brush.setShadow === 'function', 'setShadow is a method'); - }); - QUnit.test('fabric pencil brush constructor', function(assert) { - assert.ok(fabric.PencilBrush); - var brush = new fabric.PencilBrush(canvas); - assert.equal(brush.canvas, canvas, 'assigns canvas'); - assert.deepEqual(brush._points, [], 'points is an empty array'); - }); - QUnit.test('fabric pencil brush draw point', function(assert) { - var brush = new fabric.PencilBrush(canvas); - var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); - brush.onMouseDown(pointer, { e: {} }); - var pathData = brush.convertPointsToSVGPath(brush._points).join(''); - assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point'); - }); - QUnit.test('fabric pencil brush multiple points', function(assert) { - var brush = new fabric.PencilBrush(canvas); - var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); - brush.onMouseDown(pointer, { e: {} }); - brush.onMouseMove(pointer, { e: {} }); - brush.onMouseMove(pointer, { e: {} }); - brush.onMouseMove(pointer, { e: {} }); - brush.onMouseMove(pointer, { e: {} }); - var pathData = brush.convertPointsToSVGPath(brush._points).join(''); - assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point'); - assert.equal(brush._points.length, 2, 'concident points are discarded'); - }); - QUnit.test('fabric pencil brush multiple points not discarded', function(assert) { - var brush = new fabric.PencilBrush(canvas); - var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); - var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15}); - var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20}); - brush.onMouseDown(pointer, { e: {} }); - brush.onMouseMove(pointer2, { e: {} }); - brush.onMouseMove(pointer3, { e: {} }); - brush.onMouseMove(pointer2, { e: {} }); - brush.onMouseMove(pointer3, { e: {} }); - var pathData = brush.convertPointsToSVGPath(brush._points).join(''); - assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001', 'path data create a complex path'); - assert.equal(brush._points.length, 6, 'concident points are discarded'); - }); - QUnit.test('fabric pencil brush multiple points not discarded', function(assert) { - var fired = false; - var added = null; - canvas.on('path:created', function(opt) { - fired = true; - added = opt.path; + var brush = new fabric.BaseBrush(); + + assert.ok(brush instanceof fabric.BaseBrush, 'should inherit from fabric.BaseBrush'); + assert.equal(brush.color, 'rgb(0, 0, 0)', 'default color is black'); + assert.equal(brush.width, 1, 'default width is 1'); + assert.ok(typeof brush.setShadow === 'function', 'setShadow is a method'); + }); + QUnit.test('fabric pencil brush constructor', function(assert) { + assert.ok(fabric.PencilBrush); + var brush = new fabric.PencilBrush(canvas); + assert.equal(brush.canvas, canvas, 'assigns canvas'); + assert.deepEqual(brush._points, [], 'points is an empty array'); + }); + + [true, false].forEach(function(val) { + QUnit.module('fabric.BaseBrush with canvas.enableRetinaScaling = ' + val, function(hooks) { + hooks.beforeEach(function() { + canvas.enableRetinaScaling = val; + }); + QUnit.test('fabric pencil brush draw point', function(assert) { + var brush = new fabric.PencilBrush(canvas); + var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); + brush.onMouseDown(pointer, { e: {} }); + var pathData = brush.convertPointsToSVGPath(brush._points).join(''); + assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point'); + }); + QUnit.test('fabric pencil brush multiple points', function(assert) { + var brush = new fabric.PencilBrush(canvas); + var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); + brush.onMouseDown(pointer, { e: {} }); + brush.onMouseMove(pointer, { e: {} }); + brush.onMouseMove(pointer, { e: {} }); + brush.onMouseMove(pointer, { e: {} }); + brush.onMouseMove(pointer, { e: {} }); + var pathData = brush.convertPointsToSVGPath(brush._points).join(''); + assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point'); + assert.equal(brush._points.length, 2, 'concident points are discarded'); + }); + QUnit.test('fabric pencil brush multiple points not discarded', function(assert) { + var brush = new fabric.PencilBrush(canvas); + var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); + var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15}); + var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20}); + brush.onMouseDown(pointer, { e: {} }); + brush.onMouseMove(pointer2, { e: {} }); + brush.onMouseMove(pointer3, { e: {} }); + brush.onMouseMove(pointer2, { e: {} }); + brush.onMouseMove(pointer3, { e: {} }); + var pathData = brush.convertPointsToSVGPath(brush._points).join(''); + assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001', 'path data create a complex path'); + assert.equal(brush._points.length, 6, 'concident points are discarded'); + }); + QUnit.test('fabric pencil brush multiple points not discarded', function(assert) { + var fired = false; + var added = null; + canvas.on('path:created', function(opt) { + fired = true; + added = opt.path; + }); + var brush = new fabric.PencilBrush(canvas); + var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); + var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15}); + var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20}); + brush.onMouseDown(pointer, { e: {} }); + brush.onMouseMove(pointer2, { e: {} }); + brush.onMouseMove(pointer3, { e: {} }); + brush.onMouseMove(pointer2, { e: {} }); + brush.onMouseMove(pointer3, { e: {} }); + brush.onMouseUp({ e: {} }); + assert.equal(fired, true, 'event is fired'); + assert.ok(added instanceof fabric.Path, 'a path is added'); + assert.ok(added.path.length, 6, 'path has 6 steps'); + canvas.off(); + }); + }); }); - var brush = new fabric.PencilBrush(canvas); - var pointer = canvas.getPointer({ clientX: 10, clientY: 10}); - var pointer2 = canvas.getPointer({ clientX: 15, clientY: 15}); - var pointer3 = canvas.getPointer({ clientX: 20, clientY: 20}); - brush.onMouseDown(pointer, { e: {} }); - brush.onMouseMove(pointer2, { e: {} }); - brush.onMouseMove(pointer3, { e: {} }); - brush.onMouseMove(pointer2, { e: {} }); - brush.onMouseMove(pointer3, { e: {} }); - brush.onMouseUp({ e: {} }); - assert.equal(fired, true, 'event is fired'); - assert.ok(added instanceof fabric.Path, 'a path is added'); - assert.ok(added.path.length, 6, 'path has 6 steps'); - canvas.off(); }); })(); diff --git a/test/unit/itext.js b/test/unit/itext.js index 12dd6f11ffc..f90972d1850 100644 --- a/test/unit/itext.js +++ b/test/unit/itext.js @@ -1,735 +1,772 @@ (function() { var canvas = this.canvas = new fabric.Canvas(); - QUnit.module('fabric.IText', { - afterEach: function() { - canvas.clear(); - canvas.cancelRequestedRender(); - } - }); - var ITEXT_OBJECT = { - 'version': fabric.version, - 'type': 'text', - 'originX': 'left', - 'originY': 'top', - 'left': 0, - 'top': 0, - 'width': 20, - 'height': 45.2, - 'fill': 'rgb(0,0,0)', - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'strokeLineCap': 'butt', - 'strokeDashOffset': 0, - 'strokeLineJoin': 'miter', - 'strokeMiterLimit': 4, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'shadow': null, - 'visible': true, - 'clipTo': null, - 'text': 'x', - 'fontSize': 40, - 'fontWeight': 'normal', - 'fontFamily': 'Times New Roman', - 'fontStyle': 'normal', - 'lineHeight': 1.3, - 'underline': false, - 'overline': false, - 'linethrough': false, - 'textAlign': 'left', - 'backgroundColor': '', - 'textBackgroundColor': '', - 'fillRule': 'nonzero', - 'paintFirst': 'fill', - 'globalCompositeOperation': 'source-over', - 'skewX': 0, - 'skewY': 0, - 'transformMatrix': null, - 'charSpacing': 0, + version: fabric.version, + type: 'text', + originX: 'left', + originY: 'top', + left: 0, + top: 0, + width: 20, + height: 45.2, + fill: 'rgb(0,0,0)', + stroke: null, + strokeWidth: 1, + strokeDashArray: null, + strokeLineCap: 'butt', + strokeDashOffset: 0, + strokeLineJoin: 'miter', + strokeMiterLimit: 4, + scaleX: 1, + scaleY: 1, + angle: 0, + flipX: false, + flipY: false, + opacity: 1, + shadow: null, + visible: true, + clipTo: null, + text: 'x', + fontSize: 40, + fontWeight: 'normal', + fontFamily: 'Times New Roman', + fontStyle: 'normal', + lineHeight: 1.3, + underline: false, + overline: false, + linethrough: false, + textAlign: 'left', + backgroundColor: '', + textBackgroundColor: '', + fillRule: 'nonzero', + paintFirst: 'fill', + globalCompositeOperation: 'source-over', + skewX: 0, + skewY: 0, + transformMatrix: null, + charSpacing: 0, styles: { } }; - QUnit.test('constructor', function(assert) { - var iText = new fabric.IText('test'); - assert.ok(iText instanceof fabric.IText); - assert.ok(iText instanceof fabric.Text); - }); + QUnit.module('fabric.IText', function(hooks) { + hooks.afterEach(function() { + canvas.clear(); + canvas.cancelRequestedRender(); + }); - QUnit.test('initial properties', function(assert) { - var iText = new fabric.IText('test'); - assert.ok(iText instanceof fabric.IText); + QUnit.test('constructor', function(assert) { + var iText = new fabric.IText('test'); - assert.equal(iText.text, 'test'); - assert.equal(iText.type, 'i-text'); - assert.deepEqual(iText.styles, { }); - }); + assert.ok(iText instanceof fabric.IText); + assert.ok(iText instanceof fabric.Text); + }); - QUnit.test('instances', function(assert) { - var iText = new fabric.IText('test'); + QUnit.test('initial properties', function(assert) { + var iText = new fabric.IText('test'); + assert.ok(iText instanceof fabric.IText); - // Not on a sketchpad; storing it in instances array already would leak it forever. - var instances = canvas._iTextInstances && canvas._iTextInstances; - var lastInstance = instances && instances[instances.length - 1]; - assert.equal(lastInstance, undefined); + assert.equal(iText.text, 'test'); + assert.equal(iText.type, 'i-text'); + assert.deepEqual(iText.styles, { }); + }); - canvas.add(iText); - instances = canvas._iTextInstances && canvas._iTextInstances; - lastInstance = instances && instances[instances.length - 1]; - assert.equal(lastInstance, iText); + QUnit.test('instances', function(assert) { + var iText = new fabric.IText('test'); - canvas.remove(iText); - instances = canvas._iTextInstances && canvas._iTextInstances; - lastInstance = instances && instances[instances.length - 1]; - assert.equal(lastInstance, undefined); + // Not on a sketchpad; storing it in instances array already would leak it forever. + var instances = canvas._iTextInstances && canvas._iTextInstances; + var lastInstance = instances && instances[instances.length - 1]; + assert.equal(lastInstance, undefined); - // Should survive being added again after removal. - canvas.add(iText); - lastInstance = canvas._iTextInstances && canvas._iTextInstances[canvas._iTextInstances.length - 1]; - assert.equal(lastInstance, iText); - }); + canvas.add(iText); + instances = canvas._iTextInstances && canvas._iTextInstances; + lastInstance = instances && instances[instances.length - 1]; + assert.equal(lastInstance, iText); - QUnit.test('fromObject', function(assert) { - var done = assert.async(); - assert.ok(typeof fabric.IText.fromObject === 'function'); - fabric.IText.fromObject(ITEXT_OBJECT, function(iText) { - assert.ok(iText instanceof fabric.IText); - assert.deepEqual(ITEXT_OBJECT, iText.toObject()); - done(); - }); - }); + canvas.remove(iText); + instances = canvas._iTextInstances && canvas._iTextInstances; + lastInstance = instances && instances[instances.length - 1]; + assert.equal(lastInstance, undefined); - QUnit.test('lineHeight with single line', function(assert) { - var text = new fabric.IText('text with one line'); - text.lineHeight = 2; - text.initDimensions(); - var height = text.height; - text.lineHeight = 0.5; - text.initDimensions(); - var heightNew = text.height; - assert.equal(height, heightNew, 'text height does not change with one single line'); - }); + // Should survive being added again after removal. + canvas.add(iText); + lastInstance = canvas._iTextInstances && canvas._iTextInstances[canvas._iTextInstances.length - 1]; + assert.equal(lastInstance, iText); + }); - QUnit.test('lineHeight with multi line', function(assert) { - var text = new fabric.IText('text with\ntwo lines'); - text.lineHeight = 0.1; - text.initDimensions(); - var height = text.height, - minimumHeight = text.fontSize * text._fontSizeMult; - assert.equal(height > minimumHeight, true, 'text height is always bigger than minimum Height'); - }); + QUnit.test('fromObject', function(assert) { + var done = assert.async(); + assert.ok(typeof fabric.IText.fromObject === 'function'); + fabric.IText.fromObject(ITEXT_OBJECT, function(iText) { + assert.ok(iText instanceof fabric.IText); + assert.deepEqual(ITEXT_OBJECT, iText.toObject()); + done(); + }); + }); - QUnit.test('toObject', function(assert) { - var styles = { - 0: { - 0: { fill: 'red' }, - 1: { textDecoration: 'underline' } - } - }; - var iText = new fabric.IText('test', { - styles: styles - }); - assert.equal(typeof iText.toObject, 'function'); - var obj = iText.toObject(); - assert.deepEqual(obj.styles, styles); - assert.notEqual(obj.styles[0], styles[0]); - assert.notEqual(obj.styles[0][1], styles[0][1]); - assert.deepEqual(obj.styles[0], styles[0]); - assert.deepEqual(obj.styles[0][1], styles[0][1]); - }); + QUnit.test('lineHeight with single line', function(assert) { + var text = new fabric.IText('text with one line'); + text.lineHeight = 2; + text.initDimensions(); + var height = text.height; + text.lineHeight = 0.5; + text.initDimensions(); + var heightNew = text.height; + assert.equal(height, heightNew, 'text height does not change with one single line'); + }); - QUnit.test('setSelectionStart', function(assert) { - var iText = new fabric.IText('test'); + QUnit.test('lineHeight with multi line', function(assert) { + var text = new fabric.IText('text with\ntwo lines'); + text.lineHeight = 0.1; + text.initDimensions(); + var height = text.height, + minimumHeight = text.fontSize * text._fontSizeMult; + assert.equal(height > minimumHeight, true, 'text height is always bigger than minimum Height'); + }); - assert.equal(typeof iText.setSelectionStart, 'function'); + QUnit.test('toObject', function(assert) { + var styles = { + 0: { + 0: { fill: 'red' }, + 1: { textDecoration: 'underline' } + } + }; + var iText = new fabric.IText('test', { + styles: styles + }); + assert.equal(typeof iText.toObject, 'function'); + var obj = iText.toObject(); + assert.deepEqual(obj.styles, styles); + assert.notEqual(obj.styles[0], styles[0]); + assert.notEqual(obj.styles[0][1], styles[0][1]); + assert.deepEqual(obj.styles[0], styles[0]); + assert.deepEqual(obj.styles[0][1], styles[0][1]); + }); - assert.equal(iText.selectionStart, 0); + QUnit.test('setSelectionStart', function(assert) { + var iText = new fabric.IText('test'); - iText.setSelectionStart(3); - assert.equal(iText.selectionStart, 3); - assert.equal(iText.selectionEnd, 0); - }); + assert.equal(typeof iText.setSelectionStart, 'function'); - QUnit.test('empty itext', function(assert) { - var iText = new fabric.IText(''); - assert.equal(iText.width, iText.cursorWidth); - }); + assert.equal(iText.selectionStart, 0); - QUnit.test('setSelectionEnd', function(assert) { - var iText = new fabric.IText('test'); + iText.setSelectionStart(3); + assert.equal(iText.selectionStart, 3); + assert.equal(iText.selectionEnd, 0); + }); - assert.equal(typeof iText.setSelectionEnd, 'function'); + QUnit.test('empty itext', function(assert) { + var iText = new fabric.IText(''); + assert.equal(iText.width, iText.cursorWidth); + }); - assert.equal(iText.selectionEnd, 0); + QUnit.test('setSelectionEnd', function(assert) { + var iText = new fabric.IText('test'); - iText.setSelectionEnd(3); - assert.equal(iText.selectionEnd, 3); - assert.equal(iText.selectionStart, 0); - }); + assert.equal(typeof iText.setSelectionEnd, 'function'); - QUnit.test('get2DCursorLocation', function(assert) { - var iText = new fabric.IText('test\nfoo\nbarbaz'); - var loc = iText.get2DCursorLocation(); + assert.equal(iText.selectionEnd, 0); - assert.equal(loc.lineIndex, 0); - assert.equal(loc.charIndex, 0); + iText.setSelectionEnd(3); + assert.equal(iText.selectionEnd, 3); + assert.equal(iText.selectionStart, 0); + }); - // 'tes|t' - iText.selectionStart = iText.selectionEnd = 3; - loc = iText.get2DCursorLocation(); + QUnit.test('get2DCursorLocation', function(assert) { + var iText = new fabric.IText('test\nfoo\nbarbaz'); + var loc = iText.get2DCursorLocation(); - assert.equal(loc.lineIndex, 0); - assert.equal(loc.charIndex, 3); + assert.equal(loc.lineIndex, 0); + assert.equal(loc.charIndex, 0); - // test - // fo|o - iText.selectionStart = iText.selectionEnd = 7; - loc = iText.get2DCursorLocation(); + // 'tes|t' + iText.selectionStart = iText.selectionEnd = 3; + loc = iText.get2DCursorLocation(); - assert.equal(loc.lineIndex, 1); - assert.equal(loc.charIndex, 2); + assert.equal(loc.lineIndex, 0); + assert.equal(loc.charIndex, 3); - // test - // foo - // barba|z - iText.selectionStart = iText.selectionEnd = 14; - loc = iText.get2DCursorLocation(); + // test + // fo|o + iText.selectionStart = iText.selectionEnd = 7; + loc = iText.get2DCursorLocation(); - assert.equal(loc.lineIndex, 2); - assert.equal(loc.charIndex, 5); - }); + assert.equal(loc.lineIndex, 1); + assert.equal(loc.charIndex, 2); - QUnit.test('isEmptyStyles', function(assert) { - var iText = new fabric.IText('test'); - assert.ok(iText.isEmptyStyles()); + // test + // foo + // barba|z + iText.selectionStart = iText.selectionEnd = 14; + loc = iText.get2DCursorLocation(); - iText = new fabric.IText('test', { - styles: { - 0: { - 0: { } - }, - 1: { - 0: { }, 1: { } - } - } + assert.equal(loc.lineIndex, 2); + assert.equal(loc.charIndex, 5); }); - assert.ok(iText.isEmptyStyles()); - iText = new fabric.IText('test', { - styles: { - 0: { - 0: { } - }, - 1: { - 0: { fill: 'red' }, 1: { } + QUnit.test('isEmptyStyles', function(assert) { + var iText = new fabric.IText('test'); + assert.ok(iText.isEmptyStyles()); + + iText = new fabric.IText('test', { + styles: { + 0: { + 0: { } + }, + 1: { + 0: { }, 1: { } + } } - } + }); + assert.ok(iText.isEmptyStyles()); + + iText = new fabric.IText('test', { + styles: { + 0: { + 0: { } + }, + 1: { + 0: { fill: 'red' }, 1: { } + } + } + }); + assert.ok(!iText.isEmptyStyles()); }); - assert.ok(!iText.isEmptyStyles()); - }); - QUnit.test('selectAll', function(assert) { - var iText = new fabric.IText('test'); + QUnit.test('selectAll', function(assert) { + var iText = new fabric.IText('test'); - iText.selectAll(); - assert.equal(iText.selectionStart, 0); - assert.equal(iText.selectionEnd, 4); + iText.selectAll(); + assert.equal(iText.selectionStart, 0); + assert.equal(iText.selectionEnd, 4); - iText.selectionStart = 1; - iText.selectionEnd = 2; + iText.selectionStart = 1; + iText.selectionEnd = 2; - iText.selectAll(); - assert.equal(iText.selectionStart, 0); - assert.equal(iText.selectionEnd, 4); + iText.selectAll(); + assert.equal(iText.selectionStart, 0); + assert.equal(iText.selectionEnd, 4); - assert.equal(iText.selectAll(), iText, 'should be chainable'); - }); - - QUnit.test('getSelectedText', function(assert) { - var iText = new fabric.IText('test\nfoobarbaz'); - iText.selectionStart = 1; - iText.selectionEnd = 10; - assert.equal(iText.getSelectedText(), 'est\nfooba'); + assert.equal(iText.selectAll(), iText, 'should be chainable'); + }); - iText.selectionStart = iText.selectionEnd = 3; - assert.equal(iText.getSelectedText(), ''); - }); + QUnit.test('getSelectedText', function(assert) { + var iText = new fabric.IText('test\nfoobarbaz'); + iText.selectionStart = 1; + iText.selectionEnd = 10; + assert.equal(iText.getSelectedText(), 'est\nfooba'); - QUnit.test('enterEditing, exitEditing', function(assert) { - var iText = new fabric.IText('test'); + iText.selectionStart = iText.selectionEnd = 3; + assert.equal(iText.getSelectedText(), ''); + }); - assert.equal(typeof iText.enterEditing, 'function'); - assert.equal(typeof iText.exitEditing, 'function'); + QUnit.test('enterEditing, exitEditing', function(assert) { + var iText = new fabric.IText('test'); - assert.ok(!iText.isEditing); + assert.equal(typeof iText.enterEditing, 'function'); + assert.equal(typeof iText.exitEditing, 'function'); - iText.enterEditing(); - assert.ok(iText.isEditing); + assert.ok(!iText.isEditing); - iText.exitEditing(); - assert.ok(!iText.isEditing); - iText.abortCursorAnimation(); - }); + iText.enterEditing(); + assert.ok(iText.isEditing); - QUnit.test('enterEditing, exitEditing and saved props', function(assert) { - var iText = new fabric.IText('test'); - - var _savedProps = { - hasControls: iText.hasControls, - borderColor: iText.borderColor, - lockMovementX: iText.lockMovementX, - lockMovementY: iText.lockMovementY, - hoverCursor: iText.hoverCursor, - selectable: iText.selectable, - defaultCursor: iText.canvas && iText.canvas.defaultCursor, - moveCursor: iText.canvas && iText.canvas.moveCursor - }; - iText.enterEditing(); - assert.deepEqual(_savedProps, iText._savedProps, 'iText saves a copy of important props'); - assert.equal(iText.selectable, false, 'selectable is set to false'); - assert.equal(iText.hasControls, false, 'hasControls is set to false'); - iText.exitEditing(); - iText.abortCursorAnimation(); - assert.equal(iText.selectable, true, 'selectable is set back to true'); - assert.equal(iText.hasControls, true, 'hasControls is set back to true'); - iText.selectable = false; - iText.enterEditing(); - iText.exitEditing(); - assert.equal(iText.selectable, false, 'selectable is set back to initial value'); - iText.abortCursorAnimation(); - }); + iText.exitEditing(); + assert.ok(!iText.isEditing); + iText.abortCursorAnimation(); + }); - QUnit.test('event firing', function(assert) { - var iText = new fabric.IText('test'), - enter = 0, exit = 0, modify = 0; - - function countEnter() { - enter++; - } - - function countExit() { - exit++; - } - - function countModify() { - modify++; - } - - iText.on('editing:entered', countEnter); - iText.on('editing:exited', countExit); - iText.on('modified', countModify); - - assert.equal(typeof iText.enterEditing, 'function'); - assert.equal(typeof iText.exitEditing, 'function'); - - iText.enterEditing(); - assert.equal(enter, 1); - assert.equal(exit, 0); - assert.equal(modify, 0); - - iText.exitEditing(); - assert.equal(enter, 1); - assert.equal(exit, 1); - assert.equal(modify, 0); - - iText.enterEditing(); - assert.equal(enter, 2); - assert.equal(exit, 1); - assert.equal(modify, 0); - - iText.text = 'Test+'; - iText.exitEditing(); - assert.equal(enter, 2); - assert.equal(exit, 2); - assert.equal(modify, 1); - iText.abortCursorAnimation(); - }); + QUnit.test('enterEditing, exitEditing and saved props', function(assert) { + var iText = new fabric.IText('test'); + + var _savedProps = { + hasControls: iText.hasControls, + borderColor: iText.borderColor, + lockMovementX: iText.lockMovementX, + lockMovementY: iText.lockMovementY, + hoverCursor: iText.hoverCursor, + selectable: iText.selectable, + defaultCursor: iText.canvas && iText.canvas.defaultCursor, + moveCursor: iText.canvas && iText.canvas.moveCursor + }; + iText.enterEditing(); + assert.deepEqual(_savedProps, iText._savedProps, 'iText saves a copy of important props'); + assert.equal(iText.selectable, false, 'selectable is set to false'); + assert.equal(iText.hasControls, false, 'hasControls is set to false'); + iText.exitEditing(); + iText.abortCursorAnimation(); + assert.equal(iText.selectable, true, 'selectable is set back to true'); + assert.equal(iText.hasControls, true, 'hasControls is set back to true'); + iText.selectable = false; + iText.enterEditing(); + iText.exitEditing(); + assert.equal(iText.selectable, false, 'selectable is set back to initial value'); + iText.abortCursorAnimation(); + }); - QUnit.test('insertNewlineStyleObject', function(assert) { - var iText = new fabric.IText('test\n2'); + QUnit.test('event firing', function(assert) { + var iText = new fabric.IText('test'), + enter = 0, exit = 0, modify = 0; - assert.equal(typeof iText.insertNewlineStyleObject, 'function'); + function countEnter() { + enter++; + } - iText.insertNewlineStyleObject(0, 4, true); - assert.deepEqual(iText.styles, { }, 'does not insert empty styles'); - iText.styles = { 1: { 0: { fill: 'blue' } } }; - iText.insertNewlineStyleObject(0, 4, true); - assert.deepEqual(iText.styles, { 2: { 0: { fill: 'blue' } } }, 'correctly shift styles'); - }); + function countExit() { + exit++; + } - QUnit.test('shiftLineStyles', function(assert) { - var iText = new fabric.IText('test\ntest\ntest', { - styles: { - '1': { - '0': { 'fill': 'red' }, - '1': { 'fill': 'red' }, - '2': { 'fill': 'red' }, - '3': { 'fill': 'red' } - } + function countModify() { + modify++; } + + iText.on('editing:entered', countEnter); + iText.on('editing:exited', countExit); + iText.on('modified', countModify); + + assert.equal(typeof iText.enterEditing, 'function'); + assert.equal(typeof iText.exitEditing, 'function'); + + iText.enterEditing(); + assert.equal(enter, 1); + assert.equal(exit, 0); + assert.equal(modify, 0); + + iText.exitEditing(); + assert.equal(enter, 1); + assert.equal(exit, 1); + assert.equal(modify, 0); + + iText.enterEditing(); + assert.equal(enter, 2); + assert.equal(exit, 1); + assert.equal(modify, 0); + + iText.text = 'Test+'; + iText.exitEditing(); + assert.equal(enter, 2); + assert.equal(exit, 2); + assert.equal(modify, 1); + iText.abortCursorAnimation(); }); - assert.equal(typeof iText.shiftLineStyles, 'function'); + QUnit.test('insertNewlineStyleObject', function(assert) { + var iText = new fabric.IText('test\n2'); - iText.shiftLineStyles(0, +1); - assert.deepEqual(iText.styles, { - '2': { - '0': { 'fill': 'red' }, - '1': { 'fill': 'red' }, - '2': { 'fill': 'red' }, - '3': { 'fill': 'red' } - } - }); + assert.equal(typeof iText.insertNewlineStyleObject, 'function'); - iText.shiftLineStyles(0, -1); - assert.deepEqual(iText.styles, { - '1': { - '0': { 'fill': 'red' }, - '1': { 'fill': 'red' }, - '2': { 'fill': 'red' }, - '3': { 'fill': 'red' } - } + iText.insertNewlineStyleObject(0, 4, true); + assert.deepEqual(iText.styles, { }, 'does not insert empty styles'); + iText.styles = { 1: { 0: { fill: 'blue' } } }; + iText.insertNewlineStyleObject(0, 4, true); + assert.deepEqual(iText.styles, { 2: { 0: { fill: 'blue' } } }, 'correctly shift styles'); }); - }); - - QUnit.test('selectWord', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + QUnit.test('shiftLineStyles', function(assert) { + var iText = new fabric.IText('test\ntest\ntest', { + styles: { + 1: { + 0: { fill: 'red' }, + 1: { fill: 'red' }, + 2: { fill: 'red' }, + 3: { fill: 'red' } + } + } + }); - assert.equal(typeof iText.selectWord, 'function'); + assert.equal(typeof iText.shiftLineStyles, 'function'); - iText.selectWord(1); - assert.equal(iText.selectionStart, 0); // |test| - assert.equal(iText.selectionEnd, 4); + iText.shiftLineStyles(0, +1); + assert.deepEqual(iText.styles, { + 2: { + 0: { fill: 'red' }, + 1: { fill: 'red' }, + 2: { fill: 'red' }, + 3: { fill: 'red' } + } + }); - iText.selectWord(7); - assert.equal(iText.selectionStart, 5); // |foo| - assert.equal(iText.selectionEnd, 8); - }); + iText.shiftLineStyles(0, -1); + assert.deepEqual(iText.styles, { + 1: { + 0: { fill: 'red' }, + 1: { fill: 'red' }, + 2: { fill: 'red' }, + 3: { fill: 'red' } + } + }); + }); - QUnit.test('selectLine', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + QUnit.test('selectWord', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - assert.equal(typeof iText.selectLine, 'function'); + assert.equal(typeof iText.selectWord, 'function'); - iText.selectLine(6); - assert.equal(iText.selectionStart, 0); // |test foo bar-baz| - assert.equal(iText.selectionEnd, 16); + iText.selectWord(1); + assert.equal(iText.selectionStart, 0); // |test| + assert.equal(iText.selectionEnd, 4); - iText.selectLine(18); - assert.equal(iText.selectionStart, 17); // |qux| - assert.equal(iText.selectionEnd, 20); + iText.selectWord(7); + assert.equal(iText.selectionStart, 5); // |foo| + assert.equal(iText.selectionEnd, 8); + }); - assert.equal(iText.selectLine(0), iText, 'should be chainable'); - }); + QUnit.test('selectLine', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - QUnit.test('findWordBoundaryLeft', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + assert.equal(typeof iText.selectLine, 'function'); - assert.equal(typeof iText.findWordBoundaryLeft, 'function'); + iText.selectLine(6); + assert.equal(iText.selectionStart, 0); // |test foo bar-baz| + assert.equal(iText.selectionEnd, 16); - assert.equal(iText.findWordBoundaryLeft(3), 0); // 'tes|t' - assert.equal(iText.findWordBoundaryLeft(20), 17); // 'qux|' - assert.equal(iText.findWordBoundaryLeft(6), 5); // 'f|oo' - assert.equal(iText.findWordBoundaryLeft(11), 9); // 'ba|r-baz' - }); + iText.selectLine(18); + assert.equal(iText.selectionStart, 17); // |qux| + assert.equal(iText.selectionEnd, 20); - QUnit.test('findWordBoundaryRight', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + assert.equal(iText.selectLine(0), iText, 'should be chainable'); + }); - assert.equal(typeof iText.findWordBoundaryRight, 'function'); + QUnit.test('findWordBoundaryLeft', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - assert.equal(iText.findWordBoundaryRight(3), 4); // 'tes|t' - assert.equal(iText.findWordBoundaryRight(17), 20); // '|qux' - assert.equal(iText.findWordBoundaryRight(6), 8); // 'f|oo' - assert.equal(iText.findWordBoundaryRight(11), 16); // 'ba|r-baz' - }); + assert.equal(typeof iText.findWordBoundaryLeft, 'function'); - QUnit.test('findLineBoundaryLeft', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + assert.equal(iText.findWordBoundaryLeft(3), 0); // 'tes|t' + assert.equal(iText.findWordBoundaryLeft(20), 17); // 'qux|' + assert.equal(iText.findWordBoundaryLeft(6), 5); // 'f|oo' + assert.equal(iText.findWordBoundaryLeft(11), 9); // 'ba|r-baz' + }); - assert.equal(typeof iText.findLineBoundaryLeft, 'function'); + QUnit.test('findWordBoundaryRight', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - assert.equal(iText.findLineBoundaryLeft(3), 0); // 'tes|t' - assert.equal(iText.findLineBoundaryLeft(20), 17); // 'qux|' - }); + assert.equal(typeof iText.findWordBoundaryRight, 'function'); - QUnit.test('findLineBoundaryRight', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); + assert.equal(iText.findWordBoundaryRight(3), 4); // 'tes|t' + assert.equal(iText.findWordBoundaryRight(17), 20); // '|qux' + assert.equal(iText.findWordBoundaryRight(6), 8); // 'f|oo' + assert.equal(iText.findWordBoundaryRight(11), 16); // 'ba|r-baz' + }); - assert.equal(typeof iText.findLineBoundaryRight, 'function'); + QUnit.test('findLineBoundaryLeft', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - assert.equal(iText.findLineBoundaryRight(3), 16); // 'tes|t' - assert.equal(iText.findLineBoundaryRight(17), 20); // '|qux' - }); + assert.equal(typeof iText.findLineBoundaryLeft, 'function'); - QUnit.test('getSelectionStyles with no arguments', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { textDecoration: 'underline' }, - 2: { textDecoration: 'overline' }, - 4: { textBackgroundColor: '#ffc' } - }, - 1: { - 0: { fill: 'red' }, - 1: { fill: 'green' }, - 2: { fill: 'blue' } - } - } + assert.equal(iText.findLineBoundaryLeft(3), 0); // 'tes|t' + assert.equal(iText.findLineBoundaryLeft(20), 17); // 'qux|' }); - assert.equal(typeof iText.getSelectionStyles, 'function'); + QUnit.test('findLineBoundaryRight', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); - iText.selectionStart = 0; - iText.selectionEnd = 0; + assert.equal(typeof iText.findLineBoundaryRight, 'function'); - assert.deepEqual(iText.getSelectionStyles(), []); + assert.equal(iText.findLineBoundaryRight(3), 16); // 'tes|t' + assert.equal(iText.findLineBoundaryRight(17), 20); // '|qux' + }); - iText.selectionStart = 2; - iText.selectionEnd = 3; + QUnit.test('getSelectionStyles with no arguments', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { textDecoration: 'underline' }, + 2: { textDecoration: 'overline' }, + 4: { textBackgroundColor: '#ffc' } + }, + 1: { + 0: { fill: 'red' }, + 1: { fill: 'green' }, + 2: { fill: 'blue' } + } + } + }); - assert.deepEqual(iText.getSelectionStyles(), [{ - textDecoration: 'overline' - }]); + assert.equal(typeof iText.getSelectionStyles, 'function'); - iText.selectionStart = 17; - iText.selectionEnd = 18; + iText.selectionStart = 0; + iText.selectionEnd = 0; - assert.deepEqual(iText.getSelectionStyles(), [{ - fill: 'red' - }]); - }); + assert.deepEqual(iText.getSelectionStyles(), []); - QUnit.test('getSelectionStyles with 2 args', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { textDecoration: 'underline' }, - 2: { textDecoration: 'overline' }, - 4: { textBackgroundColor: '#ffc' } - }, - 1: { - 0: { fill: 'red' }, - 1: { fill: 'green' }, - 2: { fill: 'blue' } - } - } - }); + iText.selectionStart = 2; + iText.selectionEnd = 3; - assert.deepEqual(iText.getSelectionStyles(0, 2), [ - { textDecoration: 'underline' }, - { } - ]); - }); + assert.deepEqual(iText.getSelectionStyles(), [{ + textDecoration: 'overline' + }]); - QUnit.test('setSelectionStyles', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { fill: '#112233' }, - 2: { stroke: '#223344' } - } - } - }); + iText.selectionStart = 17; + iText.selectionEnd = 18; - assert.equal(typeof iText.setSelectionStyles, 'function'); - - iText.setSelectionStyles({ - fill: 'red', - stroke: 'yellow' + assert.deepEqual(iText.getSelectionStyles(), [{ + fill: 'red' + }]); }); - assert.deepEqual(iText.styles[0][0], { - fill: '#112233' - }); + QUnit.test('getSelectionStyles with 2 args', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { textDecoration: 'underline' }, + 2: { textDecoration: 'overline' }, + 4: { textBackgroundColor: '#ffc' } + }, + 1: { + 0: { fill: 'red' }, + 1: { fill: 'green' }, + 2: { fill: 'blue' } + } + } + }); - iText.selectionEnd = 0; - iText.selectionEnd = 1; - iText.setSelectionStyles({ - fill: 'red', - stroke: 'yellow' + assert.deepEqual(iText.getSelectionStyles(0, 2), [ + { textDecoration: 'underline' }, + { } + ]); }); - assert.deepEqual(iText.styles[0][0], { - fill: 'red', - stroke: 'yellow' + QUnit.test('setSelectionStyles', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { fill: '#112233' }, + 2: { stroke: '#223344' } + } + } + }); + + assert.equal(typeof iText.setSelectionStyles, 'function'); + + iText.setSelectionStyles({ + fill: 'red', + stroke: 'yellow' + }); + + assert.deepEqual(iText.styles[0][0], { + fill: '#112233' + }); + + iText.selectionEnd = 0; + iText.selectionEnd = 1; + iText.setSelectionStyles({ + fill: 'red', + stroke: 'yellow' + }); + + assert.deepEqual(iText.styles[0][0], { + fill: 'red', + stroke: 'yellow' + }); + + iText.selectionStart = 2; + iText.selectionEnd = 3; + + iText.setSelectionStyles({ + fill: '#998877', + stroke: 'yellow' + }); + + assert.deepEqual(iText.styles[0][2], { + fill: '#998877', + stroke: 'yellow' + }); }); - iText.selectionStart = 2; - iText.selectionEnd = 3; + QUnit.test('getCurrentCharFontSize', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { fontSize: 20 }, + 1: { fontSize: 60 } + } + } + }); + + assert.equal(typeof iText.getCurrentCharFontSize, 'function'); + iText.selectionStart = 0; + assert.equal(iText.getCurrentCharFontSize(), 20); + iText.selectionStart = 1; + assert.equal(iText.getCurrentCharFontSize(), 20); + iText.selectionStart = 2; + assert.equal(iText.getCurrentCharFontSize(), 60); + iText.selectionStart = 3; + assert.equal(iText.getCurrentCharFontSize(), 40); + }); - iText.setSelectionStyles({ - fill: '#998877', - stroke: 'yellow' + QUnit.test('object removal from canvas', function(assert) { + canvas.clear(); + canvas._iTextInstances = null; + var text1 = new fabric.IText('Text Will be here'); + var text2 = new fabric.IText('Text Will be here'); + assert.ok(!canvas._iTextInstances, 'canvas has no iText instances'); + assert.ok(!canvas._hasITextHandlers, 'canvas has no handlers'); + + canvas.add(text1); + assert.deepEqual(canvas._iTextInstances, [text1], 'canvas has 1 text instance'); + assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); + assert.equal(canvas._iTextInstances.length, 1, 'just one itext object should be on canvas'); + + canvas.add(text2); + assert.deepEqual(canvas._iTextInstances, [text1, text2], 'canvas has 2 text instance'); + assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); + assert.equal(canvas._iTextInstances.length, 2, 'just two itext object should be on canvas'); + + canvas.remove(text1); + assert.deepEqual(canvas._iTextInstances, [text2], 'canvas has 1 text instance'); + assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); + assert.equal(canvas._iTextInstances.length, 1, 'just two itext object should be on canvas'); + + canvas.remove(text2); + assert.deepEqual(canvas._iTextInstances, [], 'canvas has 0 text instance'); + assert.ok(!canvas._hasITextHandlers, 'canvas has no handlers'); }); - assert.deepEqual(iText.styles[0][2], { - fill: '#998877', - stroke: 'yellow' + QUnit.test('getCurrentCharColor', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { fill: 'red' }, + 1: { fill: 'green' } + } + }, + fill: '#333', + }); + + assert.equal(typeof iText.getCurrentCharColor, 'function'); + iText.selectionStart = 0; + assert.equal(iText.getCurrentCharColor(), 'red'); + iText.selectionStart = 1; + assert.equal(iText.getCurrentCharColor(), 'red'); + iText.selectionStart = 2; + assert.equal(iText.getCurrentCharColor(), 'green'); + iText.selectionStart = 3; + assert.equal(iText.getCurrentCharColor(), '#333'); }); - }); - QUnit.test('getCurrentCharFontSize', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { fontSize: 20 }, - 1: { fontSize: 60 } - } + QUnit.test('toSVGWithFonts', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux', { + styles: { + 0: { + 0: { fill: '#112233' }, + 2: { stroke: '#223344', fontFamily: 'Engagement' }, + 3: { backgroundColor: '#00FF00' } + } + }, + fontFamily: 'Plaster' + }); + fabric.fontPaths = { + Engagement: 'path-to-engagement-font-file', + Plaster: 'path-to-plaster-font-file', + }; + canvas.add(iText); + assert.equal(typeof iText.toSVG, 'function'); + var parser; + if (fabric.isLikelyNode) { + var XmlDomParser = require('xmldom').DOMParser; + parser = new XmlDomParser(); } + else { + parser = new DOMParser(); + } + var svgString = canvas.toSVG(), + doc = parser.parseFromString(svgString, 'image/svg+xml'), + style = doc.getElementsByTagName('style')[0].firstChild.data; + assert.equal(style, '\n\t\t@font-face {\n\t\t\tfont-family: \'Plaster\';\n\t\t\tsrc: url(\'path-to-plaster-font-file\');\n\t\t}\n\t\t@font-face {\n\t\t\tfont-family: \'Engagement\';\n\t\t\tsrc: url(\'path-to-engagement-font-file\');\n\t\t}\n'); }); - assert.equal(typeof iText.getCurrentCharFontSize, 'function'); - iText.selectionStart = 0; - assert.equal(iText.getCurrentCharFontSize(), 20); - iText.selectionStart = 1; - assert.equal(iText.getCurrentCharFontSize(), 20); - iText.selectionStart = 2; - assert.equal(iText.getCurrentCharFontSize(), 60); - iText.selectionStart = 3; - assert.equal(iText.getCurrentCharFontSize(), 40); - }); - - QUnit.test('object removal from canvas', function(assert) { - canvas.clear(); - canvas._iTextInstances = null; - var text1 = new fabric.IText('Text Will be here'); - var text2 = new fabric.IText('Text Will be here'); - assert.ok(!canvas._iTextInstances, 'canvas has no iText instances'); - assert.ok(!canvas._hasITextHandlers, 'canvas has no handlers'); - - canvas.add(text1); - assert.deepEqual(canvas._iTextInstances, [text1], 'canvas has 1 text instance'); - assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); - assert.equal(canvas._iTextInstances.length, 1, 'just one itext object should be on canvas'); - - canvas.add(text2); - assert.deepEqual(canvas._iTextInstances, [text1, text2], 'canvas has 2 text instance'); - assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); - assert.equal(canvas._iTextInstances.length, 2, 'just two itext object should be on canvas'); - - canvas.remove(text1); - assert.deepEqual(canvas._iTextInstances, [text2], 'canvas has 1 text instance'); - assert.ok(canvas._hasITextHandlers, 'canvas has handlers'); - assert.equal(canvas._iTextInstances.length, 1, 'just two itext object should be on canvas'); - - canvas.remove(text2); - assert.deepEqual(canvas._iTextInstances, [], 'canvas has 0 text instance'); - assert.ok(!canvas._hasITextHandlers, 'canvas has no handlers'); - }); - - QUnit.test('getCurrentCharColor', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { fill: 'red' }, - 1: { fill: 'green' } - } - }, - fill: '#333', - }); - - assert.equal(typeof iText.getCurrentCharColor, 'function'); - iText.selectionStart = 0; - assert.equal(iText.getCurrentCharColor(), 'red'); - iText.selectionStart = 1; - assert.equal(iText.getCurrentCharColor(), 'red'); - iText.selectionStart = 2; - assert.equal(iText.getCurrentCharColor(), 'green'); - iText.selectionStart = 3; - assert.equal(iText.getCurrentCharColor(), '#333'); - }); + QUnit.test('space wrap attribute', function(assert) { + var iText = new fabric.IText('test foo bar-baz\nqux'); + iText.enterEditing(); + assert.equal(iText.hiddenTextarea.wrap, 'off', 'HiddenTextarea needs wrap off attribute'); + iText.abortCursorAnimation(); + }); - QUnit.test('toSVGWithFonts', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux', { - styles: { - 0: { - 0: { fill: '#112233' }, - 2: { stroke: '#223344', fontFamily: 'Engagement' }, - 3: { backgroundColor: '#00FF00' } - } - }, - fontFamily: 'Plaster' - }); - fabric.fontPaths = { - Engagement: 'path-to-engagement-font-file', - Plaster: 'path-to-plaster-font-file', - }; - canvas.add(iText); - assert.equal(typeof iText.toSVG, 'function'); - var parser; - if (fabric.isLikelyNode) { - var XmlDomParser = require('xmldom').DOMParser; - parser = new XmlDomParser(); - } - else { - parser = new DOMParser(); - } - var svgString = canvas.toSVG(), - doc = parser.parseFromString(svgString, 'image/svg+xml'), - style = doc.getElementsByTagName('style')[0].firstChild.data; - assert.equal(style, '\n\t\t@font-face {\n\t\t\tfont-family: \'Plaster\';\n\t\t\tsrc: url(\'path-to-plaster-font-file\');\n\t\t}\n\t\t@font-face {\n\t\t\tfont-family: \'Engagement\';\n\t\t\tsrc: url(\'path-to-engagement-font-file\');\n\t\t}\n'); - }); + QUnit.test('_removeExtraneousStyles', function(assert) { + var iText = new fabric.IText('a\nq\qo', { styles: { + 0: { 0: { fontSize: 4 } }, + 1: { 0: { fontSize: 4 } }, + 2: { 0: { fontSize: 4 } }, + 3: { 0: { fontSize: 4 } }, + 4: { 0: { fontSize: 4 } }, + } }); + assert.deepEqual(iText.styles[3], { 0: { fontSize: 4 } }, 'style line 3 exists'); + assert.deepEqual(iText.styles[4], { 0: { fontSize: 4 } }, 'style line 4 exists'); + iText._removeExtraneousStyles(); + assert.equal(iText.styles[3], undefined, 'style line 3 has been removed'); + assert.equal(iText.styles[4], undefined, 'style line 4 has been removed'); + }); - QUnit.test('space wrap attribute', function(assert) { - var iText = new fabric.IText('test foo bar-baz\nqux'); - iText.enterEditing(); - assert.equal(iText.hiddenTextarea.wrap, 'off', 'HiddenTextarea needs wrap off attribute'); - iText.abortCursorAnimation(); - }); - QUnit.test('_removeExtraneousStyles', function(assert) { - var iText = new fabric.IText('a\nq\qo', { styles: { - 0: { 0: { fontSize: 4 } }, - 1: { 0: { fontSize: 4 } }, - 2: { 0: { fontSize: 4 } }, - 3: { 0: { fontSize: 4 } }, - 4: { 0: { fontSize: 4 } }, - } }); - assert.deepEqual(iText.styles[3], { 0: { fontSize: 4 } }, 'style line 3 exists'); - assert.deepEqual(iText.styles[4], { 0: { fontSize: 4 } }, 'style line 4 exists'); - iText._removeExtraneousStyles(); - assert.equal(iText.styles[3], undefined, 'style line 3 has been removed'); - assert.equal(iText.styles[4], undefined, 'style line 4 has been removed'); - }); + QUnit.module('fabric.IText with canvas.enableRetinaScaling = false', function() { + QUnit.test('hiddenTextarea does not move DOM', function(assert) { + var iText = new fabric.IText('a', { fill: '#ffffff', fontSize: 50 }); + var canvas2 = new fabric.Canvas(null, { width: 800, height: 800, renderOnAddRemove: false, enableRetinaScaling: false }); + canvas2.setDimensions({ width: 100, height: 100 }, { cssOnly: true }); + iText.set({ + top: 400, + left: 400, + }); + canvas2.add(iText); + Object.defineProperty(canvas2.upperCanvasEl, 'clientWidth', { + get: function() { return this._clientWidth; }, + set: function(value) { return this._clientWidth = value; }, + }); + Object.defineProperty(canvas2.upperCanvasEl, 'clientHeight', { + get: function() { return this._clientHeight; }, + set: function(value) { return this._clientHeight = value; }, + }); + canvas2.upperCanvasEl._clientWidth = 100; + canvas2.upperCanvasEl._clientHeight = 100; + iText.enterEditing(); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 57, 'top is scaled with CSS'); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 50, 'left is scaled with CSS'); + iText.exitEditing(); + canvas2.upperCanvasEl._clientWidth = 200; + canvas2.upperCanvasEl._clientHeight = 200; + iText.enterEditing(); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 114, 'top is scaled with CSS'); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 100, 'left is scaled with CSS'); + iText.exitEditing(); + canvas2.dispose(); + }); + }); - QUnit.test('hiddenTextarea does not move DOM', function(assert) { - var iText = new fabric.IText('a', { fill: '#ffffff', fontSize: 50 }); - var canvas2 = new fabric.Canvas(null, { width: 800, height: 800, renderOnAddRemove: false }); - canvas2.setDimensions({ width: 100, height: 100 }, { cssOnly: true }); - iText.set({ - top: 400, - left: 400, - }); - canvas2.add(iText); - Object.defineProperty(canvas2.upperCanvasEl, 'clientWidth', { - get: function() { return this._clientWidth; }, - set: function(value) { return this._clientWidth = value; }, - }); - Object.defineProperty(canvas2.upperCanvasEl, 'clientHeight', { - get: function() { return this._clientHeight; }, - set: function(value) { return this._clientHeight = value; }, - }); - canvas2.upperCanvasEl._clientWidth = 100; - canvas2.upperCanvasEl._clientHeight = 100; - iText.enterEditing(); - assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 57, 'top is scaled with CSS'); - assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 50, 'left is scaled with CSS'); - iText.exitEditing(); - canvas2.upperCanvasEl._clientWidth = 200; - canvas2.upperCanvasEl._clientHeight = 200; - iText.enterEditing(); - assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 114, 'top is scaled with CSS'); - assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 100, 'left is scaled with CSS'); - iText.exitEditing(); - canvas2.dispose(); + QUnit.module('fabric.IText with canvas.enableRetinaScaling = true', function() { + QUnit.test('hiddenTextarea does not move DOM', function(assert) { + var iText = new fabric.IText('a', { fill: '#ffffff', fontSize: 50 }); + var canvas2 = new fabric.Canvas(null, { width: 800, height: 800, renderOnAddRemove: false, enableRetinaScaling: true }); + canvas2.setDimensions({ width: 100, height: 100 }, { cssOnly: true }); + iText.set({ + top: 400, + left: 400, + }); + canvas2.add(iText); + Object.defineProperty(canvas2.upperCanvasEl, 'clientWidth', { + get: function() { return this._clientWidth; }, + set: function(value) { return this._clientWidth = value; }, + }); + Object.defineProperty(canvas2.upperCanvasEl, 'clientHeight', { + get: function() { return this._clientHeight; }, + set: function(value) { return this._clientHeight = value; }, + }); + canvas2.upperCanvasEl._clientWidth = 100; + canvas2.upperCanvasEl._clientHeight = 100; + iText.enterEditing(); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 28, 'top is scaled with CSS'); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 25, 'left is scaled with CSS'); + iText.exitEditing(); + canvas2.upperCanvasEl._clientWidth = 200; + canvas2.upperCanvasEl._clientHeight = 200; + iText.enterEditing(); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.top)), 57, 'top is scaled with CSS'); + assert.equal(Math.round(parseInt(iText.hiddenTextarea.style.left)), 50, 'left is scaled with CSS'); + iText.exitEditing(); + canvas2.dispose(); + }); + }); }); })(); diff --git a/test/unit/itext_click_behaviour.js b/test/unit/itext_click_behaviour.js index 3294bdd36b7..edfe7b1e90e 100644 --- a/test/unit/itext_click_behaviour.js +++ b/test/unit/itext_click_behaviour.js @@ -1,245 +1,297 @@ (function(){ - - var canvas = new fabric.Canvas(); - - QUnit.module('iText click interaction', { - afterEach: function() { + var canvas; + QUnit.module('iText click interaction', function(hooks) { + hooks.beforeEach(function() { + canvas = new fabric.Canvas(null, { + enableRetinaScaling: false + }); + }); + hooks.afterEach(function() { canvas.clear(); canvas.cancelRequestedRender(); - } - }); - QUnit.test('_getNewSelectionStartFromOffset end of line', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - var index = 10; - var jlen = 20; - var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 1000 }, 500, 520, index, jlen); - assert.equal(selection, index, 'index value did not change'); - }); - QUnit.test('doubleClickHandler', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - iText.canvas = canvas; - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 10 - }; - iText.enterEditing(); - iText.doubleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 0, 'dblClcik selection start is'); - assert.equal(iText.selectionEnd, 4, 'dblClcik selection end is'); - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 60 - }; - iText.doubleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 20, 'second dblClcik selection start is'); - assert.equal(iText.selectionEnd, 26, 'second dblClcik selection end is'); - iText.exitEditing(); - }); - QUnit.test('doubleClickHandler no editing', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - iText.canvas = canvas; - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 10 - }; - iText.doubleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 0, 'dblClcik selection start is'); - assert.equal(iText.selectionEnd, 0, 'dblClcik selection end is'); - }); - QUnit.test('tripleClickHandler', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - iText.canvas = canvas; - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 10 - }; - iText.enterEditing(); - iText.tripleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 0, 'tripleClick selection start is'); - assert.equal(iText.selectionEnd, 19, 'tripleClick selection end is'); - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 60 - }; - iText.tripleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 20, 'second tripleClick selection start is'); - assert.equal(iText.selectionEnd, 31, 'second tripleClick selection end is'); - iText.exitEditing(); - }); - QUnit.test('tripleClickHandler', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - iText.canvas = canvas; - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 40, - clientY: 10 - }; - iText.tripleClickHandler({ - e: eventData - }); - assert.equal(iText.selectionStart, 0, 'tripleClick selection start is'); - assert.equal(iText.selectionEnd, 0, 'tripleClick selection end is'); - }); - QUnit.test('_getNewSelectionStartFromOffset end of line', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - var index = 10; - var jlen = 20; - var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 1000 }, 500, 520, index, jlen); - assert.equal(selection, index, 'index value did not change'); - }); - QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - var index = 10; - var jlen = 20; - var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 519 }, 500, 520, index, jlen); - assert.equal(selection, index + 1, 'index value was moved to next char, since is very near'); - }); - QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - var index = 10; - var jlen = 20; - var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 502 }, 500, 520, index, jlen); - assert.equal(selection, index, 'index value was NOT moved to next char, since is very near to first one'); - }); - QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - var index = 10; - var jlen = 10; - var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 1000 }, 500, 520, index, jlen); - assert.equal(selection, index, 'index value was NOT moved to next char, since is already at end of text'); - }); - QUnit.test('_mouseDownHandlerBefore set up selected property', function(assert) { - var iText = new fabric.IText('test need some word\nsecond line'); - assert.equal(iText.selected, undefined, 'iText has no selected property'); - canvas.setActiveObject(iText); - iText.canvas = canvas; - iText._mouseDownHandlerBefore({ e: {} }); - assert.equal(iText.selected, true, 'iText has selected property'); - assert.equal(iText.__lastSelected, undefined, 'iText has no __lastSelected property'); - }); - QUnit.test('_mouseUpHandler set selected as true', function(assert) { - var iText = new fabric.IText('test'); - iText.initDelayedCursor = function() {}; - iText.renderCursorOrSelection = function() {}; - assert.equal(iText.selected, undefined, 'iText has no selected property'); - assert.equal(iText.__lastSelected, undefined, 'iText has no __lastSelected property'); - canvas.setActiveObject(iText); - iText.canvas = canvas; - iText.mouseUpHandler({ e: {} }); - assert.equal(iText.selected, true, 'iText has selected property'); - }); - QUnit.test('_mouseUpHandler on a selected object enter edit', function(assert) { - var iText = new fabric.IText('test'); - iText.initDelayedCursor = function() {}; - iText.renderCursorOrSelection = function() {}; - assert.equal(iText.isEditing, false, 'iText not editing'); - iText.canvas = canvas; - canvas._activeObject = null; - iText.selected = true; - iText.__lastSelected = true; - iText.mouseUpHandler({ e: {} }); - assert.equal(iText.isEditing, true, 'iText entered editing'); - iText.exitEditing(); - }); - QUnit.test('_mouseUpHandler on a selected object does enter edit if there is an activeObject', function(assert) { - var iText = new fabric.IText('test'); - iText.initDelayedCursor = function() {}; - iText.renderCursorOrSelection = function() {}; - assert.equal(iText.isEditing, false, 'iText not editing'); - iText.canvas = canvas; - canvas._activeObject = new fabric.IText('test2'); - iText.selected = true; - iText.__lastSelected = true; - iText.mouseUpHandler({ e: {} }); - assert.equal(iText.isEditing, false, 'iText did not enter editing'); - iText.exitEditing(); - }); - QUnit.test('_mouseUpHandler on a selected text in a group DOES NOT enter edit', function(assert) { - var iText = new fabric.IText('test'); - iText.initDelayedCursor = function() {}; - iText.renderCursorOrSelection = function() {}; - assert.equal(iText.isEditing, false, 'iText not editing'); - iText.canvas = canvas; - iText.selected = true; - iText.__lastSelected = true; - iText.group = true; - iText.mouseUpHandler({ e: {} }); - assert.equal(iText.isEditing, false, 'iText did not entered editing'); - iText.exitEditing(); - }); - QUnit.test('_mouseUpHandler on a corner of selected text DOES NOT enter edit', function(assert) { - var iText = new fabric.IText('test'); - iText.initDelayedCursor = function() {}; - iText.renderCursorOrSelection = function() {}; - assert.equal(iText.isEditing, false, 'iText not editing'); - iText.canvas = canvas; - iText.selected = true; - iText.__lastSelected = true; - iText.__corner = 'mt'; - iText.mouseUpHandler({ e: {} }); - assert.equal(iText.isEditing, false, 'iText did not entered editing'); - iText.exitEditing(); - }); - QUnit.test('click on editing itext make selection:changed fire', function(assert) { - var done = assert.async(); - var eventData = { - which: 1, - target: canvas.upperCanvasEl, - clientX: 30, - clientY: 10 - }; - var count = 0; - var countCanvas = 0; - var iText = new fabric.IText('test test'); - canvas.on('text:selection:changed', function() { - countCanvas++; - }); - iText.on('selection:changed', function() { - count++; - }); - canvas.add(iText); - assert.equal(canvas.getActiveObject(), null, 'no active object exist'); - assert.equal(count, 0, 'no selection:changed fired yet'); - assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); - canvas._onMouseDown(eventData); - canvas._onMouseUp(eventData); - assert.equal(canvas.getActiveObject(), iText, 'Itext got selected'); - assert.equal(iText.isEditing, false, 'Itext is not editing yet'); - assert.equal(count, 0, 'no selection:changed fired yet'); - assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); - assert.equal(iText.selectionStart, 0, 'Itext did not set the selectionStart'); - assert.equal(iText.selectionEnd, 0, 'Itext did not set the selectionend'); - // make a little delay or it will act as double click and select everything - setTimeout(function() { - canvas._onMouseDown(eventData); - canvas._onMouseUp(eventData); - assert.equal(iText.isEditing, true, 'Itext entered editing'); - assert.equal(iText.selectionStart, 2, 'Itext set the selectionStart'); - assert.equal(iText.selectionEnd, 2, 'Itext set the selectionend'); - assert.equal(count, 1, 'no selection:changed fired yet'); - assert.equal(countCanvas, 1, 'no text:selection:changed fired yet'); - done(); - }, 500); + }); + QUnit.test('doubleClickHandler', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + iText.canvas = canvas; + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 10 + }; + iText.enterEditing(); + iText.doubleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 0, 'dblClcik selection start is'); + assert.equal(iText.selectionEnd, 4, 'dblClcik selection end is'); + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 60 + }; + iText.doubleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 20, 'second dblClcik selection start is'); + assert.equal(iText.selectionEnd, 26, 'second dblClcik selection end is'); + iText.exitEditing(); + }); + QUnit.test('doubleClickHandler no editing', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + iText.canvas = canvas; + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 10 + }; + iText.doubleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 0, 'dblClcik selection start is'); + assert.equal(iText.selectionEnd, 0, 'dblClcik selection end is'); + }); + QUnit.test('tripleClickHandler', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + iText.canvas = canvas; + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 10 + }; + iText.enterEditing(); + iText.tripleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 0, 'tripleClick selection start is'); + assert.equal(iText.selectionEnd, 19, 'tripleClick selection end is'); + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 60 + }; + iText.tripleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 20, 'second tripleClick selection start is'); + assert.equal(iText.selectionEnd, 31, 'second tripleClick selection end is'); + iText.exitEditing(); + }); + QUnit.test('tripleClickHandler', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + iText.canvas = canvas; + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 40, + clientY: 10 + }; + iText.tripleClickHandler({ + e: eventData + }); + assert.equal(iText.selectionStart, 0, 'tripleClick selection start is'); + assert.equal(iText.selectionEnd, 0, 'tripleClick selection end is'); + }); + QUnit.test('_getNewSelectionStartFromOffset end of line', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + var index = 10; + var jlen = 20; + var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 1000 }, 500, 520, index, jlen); + assert.equal(selection, index, 'index value did not change'); + }); + QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + var index = 10; + var jlen = 20; + var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 519 }, 500, 520, index, jlen); + assert.equal(selection, index + 1, 'index value was moved to next char, since is very near'); + }); + QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + var index = 10; + var jlen = 20; + var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 502 }, 500, 520, index, jlen); + assert.equal(selection, index, 'index value was NOT moved to next char, since is very near to first one'); + }); + QUnit.test('_getNewSelectionStartFromOffset middle of line', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + var index = 10; + var jlen = 10; + var selection = iText._getNewSelectionStartFromOffset({ y: 1, x: 1000 }, 500, 520, index, jlen); + assert.equal(selection, index, 'index value was NOT moved to next char, since is already at end of text'); + }); + QUnit.test('_mouseDownHandlerBefore set up selected property', function(assert) { + var iText = new fabric.IText('test need some word\nsecond line'); + assert.equal(iText.selected, undefined, 'iText has no selected property'); + canvas.setActiveObject(iText); + iText.canvas = canvas; + iText._mouseDownHandlerBefore({ e: {} }); + assert.equal(iText.selected, true, 'iText has selected property'); + assert.equal(iText.__lastSelected, undefined, 'iText has no __lastSelected property'); + }); + QUnit.test('_mouseUpHandler set selected as true', function(assert) { + var iText = new fabric.IText('test'); + iText.initDelayedCursor = function() {}; + iText.renderCursorOrSelection = function() {}; + assert.equal(iText.selected, undefined, 'iText has no selected property'); + assert.equal(iText.__lastSelected, undefined, 'iText has no __lastSelected property'); + canvas.setActiveObject(iText); + iText.canvas = canvas; + iText.mouseUpHandler({ e: {} }); + assert.equal(iText.selected, true, 'iText has selected property'); + }); + QUnit.test('_mouseUpHandler on a selected object enter edit', function(assert) { + var iText = new fabric.IText('test'); + iText.initDelayedCursor = function() {}; + iText.renderCursorOrSelection = function() {}; + assert.equal(iText.isEditing, false, 'iText not editing'); + iText.canvas = canvas; + canvas._activeObject = null; + iText.selected = true; + iText.__lastSelected = true; + iText.mouseUpHandler({ e: {} }); + assert.equal(iText.isEditing, true, 'iText entered editing'); + iText.exitEditing(); + }); + QUnit.test('_mouseUpHandler on a selected object does enter edit if there is an activeObject', function(assert) { + var iText = new fabric.IText('test'); + iText.initDelayedCursor = function() {}; + iText.renderCursorOrSelection = function() {}; + assert.equal(iText.isEditing, false, 'iText not editing'); + iText.canvas = canvas; + canvas._activeObject = new fabric.IText('test2'); + iText.selected = true; + iText.__lastSelected = true; + iText.mouseUpHandler({ e: {} }); + assert.equal(iText.isEditing, false, 'iText did not enter editing'); + iText.exitEditing(); + }); + QUnit.test('_mouseUpHandler on a selected text in a group DOES NOT enter edit', function(assert) { + var iText = new fabric.IText('test'); + iText.initDelayedCursor = function() {}; + iText.renderCursorOrSelection = function() {}; + assert.equal(iText.isEditing, false, 'iText not editing'); + iText.canvas = canvas; + iText.selected = true; + iText.__lastSelected = true; + iText.group = true; + iText.mouseUpHandler({ e: {} }); + assert.equal(iText.isEditing, false, 'iText did not entered editing'); + iText.exitEditing(); + }); + QUnit.test('_mouseUpHandler on a corner of selected text DOES NOT enter edit', function(assert) { + var iText = new fabric.IText('test'); + iText.initDelayedCursor = function() {}; + iText.renderCursorOrSelection = function() {}; + assert.equal(iText.isEditing, false, 'iText not editing'); + iText.canvas = canvas; + iText.selected = true; + iText.__lastSelected = true; + iText.__corner = 'mt'; + iText.mouseUpHandler({ e: {} }); + assert.equal(iText.isEditing, false, 'iText did not entered editing'); + iText.exitEditing(); + }); + + QUnit.module('iText click interaction with canvas.enableRetinaScaling = false', function(hooks) { + hooks.beforeEach(function() { + canvas.enableRetinaScaling = false; + }); + QUnit.test('click on editing itext make selection:changed fire', function(assert) { + var done = assert.async(); + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 30, + clientY: 10 + }; + var count = 0; + var countCanvas = 0; + var iText = new fabric.IText('test test'); + canvas.on('text:selection:changed', function() { + countCanvas++; + }); + iText.on('selection:changed', function() { + count++; + }); + canvas.add(iText); + assert.equal(canvas.getActiveObject(), null, 'no active object exist'); + assert.equal(count, 0, 'no selection:changed fired yet'); + assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); + canvas._onMouseDown(eventData); + canvas._onMouseUp(eventData); + assert.equal(canvas.getActiveObject(), iText, 'Itext got selected'); + assert.equal(iText.isEditing, false, 'Itext is not editing yet'); + assert.equal(count, 0, 'no selection:changed fired yet'); + assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); + assert.equal(iText.selectionStart, 0, 'Itext did not set the selectionStart'); + assert.equal(iText.selectionEnd, 0, 'Itext did not set the selectionend'); + // make a little delay or it will act as double click and select everything + setTimeout(function() { + canvas._onMouseDown(eventData); + canvas._onMouseUp(eventData); + assert.equal(iText.isEditing, true, 'Itext entered editing'); + assert.equal(iText.selectionStart, 2, 'Itext set the selectionStart'); + assert.equal(iText.selectionEnd, 2, 'Itext set the selectionend'); + assert.equal(count, 1, 'no selection:changed fired yet'); + assert.equal(countCanvas, 1, 'no text:selection:changed fired yet'); + done(); + }, 500); + }); + }); + + QUnit.module('iText click interaction with canvas.enableRetinaScaling = true', function(hooks) { + hooks.beforeEach(function() { + fabric.devicePixelRatio = 2; + canvas = new fabric.Canvas(null, { + enableRetinaScaling: true, + }); + }); + QUnit.test('click on editing itext make selection:changed fire', function(assert) { + var done = assert.async(); + var eventData = { + which: 1, + target: canvas.upperCanvasEl, + clientX: 60, + clientY: 30 + }; + var count = 0; + var countCanvas = 0; + var iText = new fabric.IText('test test'); + canvas.on('text:selection:changed', function() { + countCanvas++; + }); + iText.on('selection:changed', function() { + count++; + }); + canvas.add(iText); + assert.equal(canvas.getActiveObject(), null, 'no active object exist'); + assert.equal(count, 0, 'no selection:changed fired yet'); + assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); + canvas._onMouseDown(eventData); + canvas._onMouseUp(eventData); + assert.equal(canvas.getActiveObject(), iText, 'Itext got selected'); + assert.equal(iText.isEditing, false, 'Itext is not editing yet'); + assert.equal(count, 0, 'no selection:changed fired yet'); + assert.equal(countCanvas, 0, 'no text:selection:changed fired yet'); + assert.equal(iText.selectionStart, 0, 'Itext did not set the selectionStart'); + assert.equal(iText.selectionEnd, 0, 'Itext did not set the selectionend'); + // make a little delay or it will act as double click and select everything + setTimeout(function() { + canvas._onMouseDown(eventData); + canvas._onMouseUp(eventData); + assert.equal(iText.isEditing, true, 'Itext entered editing'); + assert.equal(iText.selectionStart, 2, 'Itext set the selectionStart'); + assert.equal(iText.selectionEnd, 2, 'Itext set the selectionend'); + assert.equal(count, 1, 'no selection:changed fired yet'); + assert.equal(countCanvas, 1, 'no text:selection:changed fired yet'); + done(); + }, 500); + }); + }); }); })();