mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-09 22:34:43 +00:00
forgot to set proper style guide when auto-formatting
This commit is contained in:
parent
84adfa180b
commit
30a95ae1e4
4 changed files with 386 additions and 386 deletions
|
|
@ -1,41 +1,41 @@
|
|||
(function () {
|
||||
|
||||
/**
|
||||
* Override _setObjectScale and add Textbox specific resizing behavior. Resizing
|
||||
* a Textbox doesn't scale text, it only changes width and makes text wrap automatically.
|
||||
*/
|
||||
var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale;
|
||||
fabric.Canvas.prototype._setObjectScale = function (localMouse, transform,
|
||||
lockScalingX, lockScalingY, by, lockScalingFlip) {
|
||||
/**
|
||||
* Override _setObjectScale and add Textbox specific resizing behavior. Resizing
|
||||
* a Textbox doesn't scale text, it only changes width and makes text wrap automatically.
|
||||
*/
|
||||
var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale;
|
||||
fabric.Canvas.prototype._setObjectScale = function (localMouse, transform,
|
||||
lockScalingX, lockScalingY, by, lockScalingFlip) {
|
||||
|
||||
var t = transform.target;
|
||||
if (t instanceof fabric.Textbox) {
|
||||
var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth));
|
||||
if (w >= t.minWidth) {
|
||||
t.set('width', w);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform,
|
||||
lockScalingX, lockScalingY, by, lockScalingFlip);
|
||||
}
|
||||
};
|
||||
var t = transform.target;
|
||||
if (t instanceof fabric.Textbox) {
|
||||
var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth));
|
||||
if (w >= t.minWidth) {
|
||||
t.set('width', w);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform,
|
||||
lockScalingX, lockScalingY, by, lockScalingFlip);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets controls of this group to the Textbox's special configuration if
|
||||
* one is present in the group. Deletes _controlsVisibility otherwise, so that
|
||||
* it gets initialized to default value at runtime.
|
||||
*/
|
||||
fabric.Group.prototype._refreshControlsVisibility = function () {
|
||||
if (typeof fabric.Textbox === 'undefined') {
|
||||
return;
|
||||
}
|
||||
for (var i = this._objects.length; i--;) {
|
||||
if (this._objects[i] instanceof fabric.Textbox) {
|
||||
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Sets controls of this group to the Textbox's special configuration if
|
||||
* one is present in the group. Deletes _controlsVisibility otherwise, so that
|
||||
* it gets initialized to default value at runtime.
|
||||
*/
|
||||
fabric.Group.prototype._refreshControlsVisibility = function () {
|
||||
if (typeof fabric.Textbox === 'undefined') {
|
||||
return;
|
||||
}
|
||||
for (var i = this._objects.length; i--;) {
|
||||
if (this._objects[i] instanceof fabric.Textbox) {
|
||||
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
(function () {
|
||||
var getNewSelectionStartFromOffsetOverriden = fabric.IText.prototype._getNewSelectionStartFromOffset;
|
||||
/**
|
||||
* Overrides the IText implementation and always sends lineIndex as 0 for Textboxes.
|
||||
* @param {Number} mouseOffset
|
||||
* @param {Number} prevWidth
|
||||
* @param {Number} width
|
||||
* @param {Number} index
|
||||
* @param {Number} lineIndex
|
||||
* @param {Number} jlen
|
||||
* @returns {Number}
|
||||
*/
|
||||
fabric.IText.prototype._getNewSelectionStartFromOffset = function (mouseOffset,
|
||||
prevWidth, width, index, lineIndex, jlen) {
|
||||
if (this instanceof fabric.Textbox) {
|
||||
lineIndex = 0;
|
||||
}
|
||||
return getNewSelectionStartFromOffsetOverriden
|
||||
.call(this, mouseOffset,
|
||||
prevWidth, width, index, lineIndex, jlen);
|
||||
};
|
||||
})();
|
||||
var getNewSelectionStartFromOffsetOverriden = fabric.IText.prototype._getNewSelectionStartFromOffset;
|
||||
/**
|
||||
* Overrides the IText implementation and always sends lineIndex as 0 for Textboxes.
|
||||
* @param {Number} mouseOffset
|
||||
* @param {Number} prevWidth
|
||||
* @param {Number} width
|
||||
* @param {Number} index
|
||||
* @param {Number} lineIndex
|
||||
* @param {Number} jlen
|
||||
* @returns {Number}
|
||||
*/
|
||||
fabric.IText.prototype._getNewSelectionStartFromOffset = function (mouseOffset,
|
||||
prevWidth, width, index, lineIndex, jlen) {
|
||||
if (this instanceof fabric.Textbox) {
|
||||
lineIndex = 0;
|
||||
}
|
||||
return getNewSelectionStartFromOffsetOverriden
|
||||
.call(this, mouseOffset,
|
||||
prevWidth, width, index, lineIndex, jlen);
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.Textbox.prototype */ {
|
||||
/**
|
||||
* Overrides superclass function and adjusts cursor offset value because
|
||||
* lines do not necessarily end with a newline in Textbox.
|
||||
* @param {Event} e
|
||||
* @param {Boolean} isRight
|
||||
* @returns {Number}
|
||||
*/
|
||||
getDownCursorOffset: function (e, isRight) {
|
||||
return fabric.IText.prototype.getDownCursorOffset.apply(this, [e, isRight]) - 1;
|
||||
},
|
||||
/**
|
||||
* Overrides superclass function and adjusts cursor offset value because
|
||||
* lines do not necessarily end with a newline in Textbox.
|
||||
* @param {Event} e
|
||||
* @param {Boolean} isRight
|
||||
* @returns {Number}
|
||||
*/
|
||||
getUpCursorOffset: function (e, isRight) {
|
||||
return fabric.IText.prototype.getUpCursorOffset.apply(this, [e, isRight]) - 1;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Overrides superclass function and adjusts cursor offset value because
|
||||
* lines do not necessarily end with a newline in Textbox.
|
||||
* @param {Event} e
|
||||
* @param {Boolean} isRight
|
||||
* @returns {Number}
|
||||
*/
|
||||
getDownCursorOffset: function (e, isRight) {
|
||||
return fabric.IText.prototype.getDownCursorOffset.apply(this, [e, isRight]) - 1;
|
||||
},
|
||||
/**
|
||||
* Overrides superclass function and adjusts cursor offset value because
|
||||
* lines do not necessarily end with a newline in Textbox.
|
||||
* @param {Event} e
|
||||
* @param {Boolean} isRight
|
||||
* @returns {Number}
|
||||
*/
|
||||
getUpCursorOffset: function (e, isRight) {
|
||||
return fabric.IText.prototype.getUpCursorOffset.apply(this, [e, isRight]) - 1;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,317 +1,317 @@
|
|||
(function () {
|
||||
var clone = fabric.util.object.clone;
|
||||
var clone = fabric.util.object.clone;
|
||||
|
||||
/**
|
||||
* Textbox class, based on IText, allows the user to resize the text rectangle
|
||||
* and wraps lines automatically. Textboxes have their Y scaling locked, the
|
||||
* user can only change width. Height is adjusted automatically based on the
|
||||
* wrapping of lines.
|
||||
* @class fabric.Textbox
|
||||
* @extends fabric.IText
|
||||
* @mixes fabric.Observable
|
||||
* @return {fabric.Textbox} thisArg
|
||||
* @see {@link fabric.Textbox#initialize} for constructor definition
|
||||
*/
|
||||
fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, {
|
||||
/**
|
||||
* Textbox class, based on IText, allows the user to resize the text rectangle
|
||||
* and wraps lines automatically. Textboxes have their Y scaling locked, the
|
||||
* user can only change width. Height is adjusted automatically based on the
|
||||
* wrapping of lines.
|
||||
* @class fabric.Textbox
|
||||
* @extends fabric.IText
|
||||
* @mixes fabric.Observable
|
||||
* @return {fabric.Textbox} thisArg
|
||||
* @see {@link fabric.Textbox#initialize} for constructor definition
|
||||
* Type of an object
|
||||
* @type String
|
||||
* @default
|
||||
*/
|
||||
fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, {
|
||||
/**
|
||||
* Type of an object
|
||||
* @type String
|
||||
* @default
|
||||
*/
|
||||
type: 'textbox',
|
||||
/**
|
||||
* Minimum width of textbox, in pixels.
|
||||
* @type Number
|
||||
* @default
|
||||
*/
|
||||
minWidth: 20,
|
||||
/**
|
||||
* Cached array of text wrapping.
|
||||
* @type Array
|
||||
*/
|
||||
__cachedLines: null,
|
||||
/**
|
||||
* Constructor. Some scaling related property values are forced. Visibility
|
||||
* of controls is also fixed; only the rotation and width controls are
|
||||
* made available.
|
||||
* @param {String} text Text string
|
||||
* @param {Object} [options] Options object
|
||||
* @return {fabric.Textbox} thisArg
|
||||
*/
|
||||
initialize: function (text, options) {
|
||||
this.ctx = fabric.util.createCanvasElement().getContext('2d');
|
||||
|
||||
this.callSuper('initialize', text, options);
|
||||
this.set({
|
||||
lockUniScaling: false,
|
||||
lockScalingY: true,
|
||||
lockScalingFlip: true,
|
||||
hasBorders: true
|
||||
});
|
||||
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
||||
|
||||
// add width to this list of props that effect line wrapping.
|
||||
this._dimensionAffectingProps.width = true;
|
||||
},
|
||||
/**
|
||||
* Unlike superclass's version of this function, Textbox does not update
|
||||
* its width.
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_initDimensions: function (ctx) {
|
||||
if (this.__skipDimension) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx) {
|
||||
ctx = fabric.util.createCanvasElement().getContext('2d');
|
||||
this._setTextStyles(ctx);
|
||||
}
|
||||
this._textLines = this._splitTextIntoLines();
|
||||
this._clearCache();
|
||||
this.height = this._getTextHeight(ctx);
|
||||
},
|
||||
/**
|
||||
* Wraps text using the 'width' property of Textbox. First this function
|
||||
* splits text on newlines, so we preserve newlines entered by the user.
|
||||
* Then it wraps each line using the width of the Textbox by calling
|
||||
* _wrapLine().
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @param {String} text The string of text that is split into lines
|
||||
* @returns {Array} Array of lines
|
||||
*/
|
||||
_wrapText: function (ctx, text) {
|
||||
var lines = text.split(this._reNewline), wrapped = [], i;
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
wrapped = wrapped.concat(this._wrapLine(ctx, lines[i]));
|
||||
}
|
||||
|
||||
return wrapped;
|
||||
},
|
||||
/**
|
||||
* Wraps a line of text using the width of the Textbox and a context.
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @param {String} text The string of text to split into lines
|
||||
* @returns {Array} Array of line(s) into which the given text is wrapped
|
||||
* to.
|
||||
*/
|
||||
_wrapLine: function (ctx, text) {
|
||||
var maxWidth = this.width, words = text.split(' '),
|
||||
lines = [],
|
||||
line = '';
|
||||
|
||||
if (ctx.measureText(text).width < maxWidth) {
|
||||
lines.push(text);
|
||||
}
|
||||
else {
|
||||
while (words.length > 0) {
|
||||
|
||||
/*
|
||||
* If the textbox's width is less than the widest letter.
|
||||
* TODO: Performance improvement - cache the width of W whenever
|
||||
* fontSize changes.
|
||||
*/
|
||||
if (maxWidth <= ctx.measureText('W').width) {
|
||||
return text.split('');
|
||||
}
|
||||
|
||||
/*
|
||||
* This handles a word that is longer than the width of the
|
||||
* text area.
|
||||
*/
|
||||
while (Math.ceil(ctx.measureText(words[0]).width) >= maxWidth) {
|
||||
var tmp = words[0];
|
||||
words[0] = tmp.slice(0, -1);
|
||||
if (words.length > 1) {
|
||||
words[1] = tmp.slice(-1) + words[1];
|
||||
}
|
||||
else {
|
||||
words.push(tmp.slice(-1));
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.ceil(ctx.measureText(line + words[0]).width) < maxWidth) {
|
||||
line += words.shift() + ' ';
|
||||
}
|
||||
else {
|
||||
lines.push(line);
|
||||
line = '';
|
||||
}
|
||||
if (words.length === 0) {
|
||||
lines.push(line.substring(0, line.length - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
},
|
||||
/**
|
||||
* Gets lines of text to render in the Textbox. This function calculates
|
||||
* text wrapping on the fly everytime it is called.
|
||||
* @returns {Array} Array of lines in the Textbox.
|
||||
* @override
|
||||
*/
|
||||
_splitTextIntoLines: function () {
|
||||
this.ctx.save();
|
||||
this._setTextStyles(this.ctx);
|
||||
|
||||
lines = this._wrapText(this.ctx, this.text);
|
||||
|
||||
this.ctx.restore();
|
||||
return lines;
|
||||
},
|
||||
|
||||
/**
|
||||
* When part of a group, we don't want the Textbox's scale to increase if
|
||||
* the group's increases. That's why we reduce the scale of the Textbox by
|
||||
* the amount that the group's increases. This is to maintain the effective
|
||||
* scale of the Textbox at 1, so that font-size values make sense. Otherwise
|
||||
* the same font-size value would result in different actual size depending
|
||||
* on the value of the scale.
|
||||
* @param {String} key
|
||||
* @param {Any} value
|
||||
*/
|
||||
setOnGroup: function (key, value) {
|
||||
if (key === 'scaleX') {
|
||||
this.set(key, Math.abs(1 / value));
|
||||
this.set('width', (this.get('width') * value) /
|
||||
(typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX));
|
||||
this.__oldScaleX = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns 2d representation (lineIndex and charIndex) of cursor (or selection start).
|
||||
* Overrides the superclass function to take into account text wrapping.
|
||||
* @param {Number} selectionStart Optional index. When not given, current selectionStart is used.
|
||||
* @returns {Object} This object has 'lineIndex' and 'charIndex' properties set to Numbers.
|
||||
*/
|
||||
get2DCursorLocation: function (selectionStart) {
|
||||
if (typeof selectionStart === 'undefined') {
|
||||
selectionStart = this.selectionStart;
|
||||
}
|
||||
|
||||
/*
|
||||
* We use `temp` to populate linesBeforeCursor instead of simply splitting
|
||||
* textBeforeCursor with newlines to handle the case of the
|
||||
* selectionStart value being on a word that, because of its length,
|
||||
* needs to be wrapped to the next line.
|
||||
*/
|
||||
var lineIndex = 0,
|
||||
linesBeforeCursor = [],
|
||||
allLines = this._textLines, temp = selectionStart;
|
||||
|
||||
while (temp >= 0) {
|
||||
if (lineIndex > allLines.length - 1) {
|
||||
break;
|
||||
}
|
||||
temp -= allLines[lineIndex].length;
|
||||
if (temp < 0) {
|
||||
linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex].slice(0,
|
||||
temp + allLines[lineIndex].length);
|
||||
}
|
||||
else {
|
||||
linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex];
|
||||
}
|
||||
lineIndex++;
|
||||
}
|
||||
lineIndex--;
|
||||
|
||||
var lastLine = linesBeforeCursor[linesBeforeCursor.length - 1],
|
||||
charIndex = lastLine.length;
|
||||
|
||||
if (linesBeforeCursor[lineIndex] === allLines[lineIndex]) {
|
||||
if (lineIndex + 1 < allLines.length - 1) {
|
||||
lineIndex++;
|
||||
charIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
lineIndex: lineIndex,
|
||||
charIndex: charIndex
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Overrides superclass function and uses text wrapping data to get cursor
|
||||
* boundary offsets instead of the array of chars.
|
||||
* @param {Array} chars Unused
|
||||
* @param {String} typeOfBoundaries Can be 'cursor' or 'selection'
|
||||
* @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set.
|
||||
*/
|
||||
_getCursorBoundariesOffsets: function (chars, typeOfBoundaries) {
|
||||
var topOffset = 0,
|
||||
leftOffset = 0,
|
||||
cursorLocation = this.get2DCursorLocation(),
|
||||
lineChars = this._textLines[cursorLocation.lineIndex].split(''),
|
||||
lineLeftOffset = this._getCachedLineOffset(cursorLocation.lineIndex);
|
||||
|
||||
for (var i = 0; i < cursorLocation.charIndex; i++) {
|
||||
leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i);
|
||||
}
|
||||
|
||||
for (i = 0; i < cursorLocation.lineIndex; i++) {
|
||||
topOffset += this._getHeightOfLine(this.ctx, i);
|
||||
}
|
||||
|
||||
if (typeOfBoundaries === 'cursor') {
|
||||
topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) / this.lineHeight
|
||||
- this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) * (1 - this._fontSizeFraction);
|
||||
}
|
||||
|
||||
return {
|
||||
top: topOffset,
|
||||
left: leftOffset,
|
||||
lineLeft: lineLeftOffset
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Returns object representation of an instance
|
||||
* @method toObject
|
||||
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
||||
* @return {Object} object representation of an instance
|
||||
*/
|
||||
toObject: function (propertiesToInclude) {
|
||||
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
|
||||
minWidth: this.minWidth
|
||||
});
|
||||
}
|
||||
});
|
||||
type: 'textbox',
|
||||
/**
|
||||
* Returns fabric.Textbox instance from an object representation
|
||||
* @static
|
||||
* @memberOf fabric.Textbox
|
||||
* @param {Object} object Object to create an instance from
|
||||
* @return {fabric.Textbox} instance of fabric.Textbox
|
||||
* Minimum width of textbox, in pixels.
|
||||
* @type Number
|
||||
* @default
|
||||
*/
|
||||
fabric.Textbox.fromObject = function (object) {
|
||||
return new fabric.Textbox(object.text, clone(object));
|
||||
};
|
||||
minWidth: 20,
|
||||
/**
|
||||
* Returns the default controls visibility required for Textboxes.
|
||||
* @returns {Object}
|
||||
*/
|
||||
fabric.Textbox.getTextboxControlVisibility = function () {
|
||||
return {
|
||||
tl: false,
|
||||
tr: false,
|
||||
br: false,
|
||||
bl: false,
|
||||
ml: true,
|
||||
mt: false,
|
||||
mr: true,
|
||||
mb: false,
|
||||
mtr: true
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Contains all fabric.Textbox objects that have been created
|
||||
* @static
|
||||
* @memberof fabric.Textbox
|
||||
* Cached array of text wrapping.
|
||||
* @type Array
|
||||
*/
|
||||
fabric.Textbox.instances = [];
|
||||
})();
|
||||
__cachedLines: null,
|
||||
/**
|
||||
* Constructor. Some scaling related property values are forced. Visibility
|
||||
* of controls is also fixed; only the rotation and width controls are
|
||||
* made available.
|
||||
* @param {String} text Text string
|
||||
* @param {Object} [options] Options object
|
||||
* @return {fabric.Textbox} thisArg
|
||||
*/
|
||||
initialize: function (text, options) {
|
||||
this.ctx = fabric.util.createCanvasElement().getContext('2d');
|
||||
|
||||
this.callSuper('initialize', text, options);
|
||||
this.set({
|
||||
lockUniScaling: false,
|
||||
lockScalingY: true,
|
||||
lockScalingFlip: true,
|
||||
hasBorders: true
|
||||
});
|
||||
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
|
||||
|
||||
// add width to this list of props that effect line wrapping.
|
||||
this._dimensionAffectingProps.width = true;
|
||||
},
|
||||
/**
|
||||
* Unlike superclass's version of this function, Textbox does not update
|
||||
* its width.
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_initDimensions: function (ctx) {
|
||||
if (this.__skipDimension) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx) {
|
||||
ctx = fabric.util.createCanvasElement().getContext('2d');
|
||||
this._setTextStyles(ctx);
|
||||
}
|
||||
this._textLines = this._splitTextIntoLines();
|
||||
this._clearCache();
|
||||
this.height = this._getTextHeight(ctx);
|
||||
},
|
||||
/**
|
||||
* Wraps text using the 'width' property of Textbox. First this function
|
||||
* splits text on newlines, so we preserve newlines entered by the user.
|
||||
* Then it wraps each line using the width of the Textbox by calling
|
||||
* _wrapLine().
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @param {String} text The string of text that is split into lines
|
||||
* @returns {Array} Array of lines
|
||||
*/
|
||||
_wrapText: function (ctx, text) {
|
||||
var lines = text.split(this._reNewline), wrapped = [], i;
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
wrapped = wrapped.concat(this._wrapLine(ctx, lines[i]));
|
||||
}
|
||||
|
||||
return wrapped;
|
||||
},
|
||||
/**
|
||||
* Wraps a line of text using the width of the Textbox and a context.
|
||||
* @param {CanvasRenderingContext2D} ctx Context to use for measurements
|
||||
* @param {String} text The string of text to split into lines
|
||||
* @returns {Array} Array of line(s) into which the given text is wrapped
|
||||
* to.
|
||||
*/
|
||||
_wrapLine: function (ctx, text) {
|
||||
var maxWidth = this.width, words = text.split(' '),
|
||||
lines = [],
|
||||
line = '';
|
||||
|
||||
if (ctx.measureText(text).width < maxWidth) {
|
||||
lines.push(text);
|
||||
}
|
||||
else {
|
||||
while (words.length > 0) {
|
||||
|
||||
/*
|
||||
* If the textbox's width is less than the widest letter.
|
||||
* TODO: Performance improvement - cache the width of W whenever
|
||||
* fontSize changes.
|
||||
*/
|
||||
if (maxWidth <= ctx.measureText('W').width) {
|
||||
return text.split('');
|
||||
}
|
||||
|
||||
/*
|
||||
* This handles a word that is longer than the width of the
|
||||
* text area.
|
||||
*/
|
||||
while (Math.ceil(ctx.measureText(words[0]).width) >= maxWidth) {
|
||||
var tmp = words[0];
|
||||
words[0] = tmp.slice(0, -1);
|
||||
if (words.length > 1) {
|
||||
words[1] = tmp.slice(-1) + words[1];
|
||||
}
|
||||
else {
|
||||
words.push(tmp.slice(-1));
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.ceil(ctx.measureText(line + words[0]).width) < maxWidth) {
|
||||
line += words.shift() + ' ';
|
||||
}
|
||||
else {
|
||||
lines.push(line);
|
||||
line = '';
|
||||
}
|
||||
if (words.length === 0) {
|
||||
lines.push(line.substring(0, line.length - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
},
|
||||
/**
|
||||
* Gets lines of text to render in the Textbox. This function calculates
|
||||
* text wrapping on the fly everytime it is called.
|
||||
* @returns {Array} Array of lines in the Textbox.
|
||||
* @override
|
||||
*/
|
||||
_splitTextIntoLines: function () {
|
||||
this.ctx.save();
|
||||
this._setTextStyles(this.ctx);
|
||||
|
||||
lines = this._wrapText(this.ctx, this.text);
|
||||
|
||||
this.ctx.restore();
|
||||
return lines;
|
||||
},
|
||||
|
||||
/**
|
||||
* When part of a group, we don't want the Textbox's scale to increase if
|
||||
* the group's increases. That's why we reduce the scale of the Textbox by
|
||||
* the amount that the group's increases. This is to maintain the effective
|
||||
* scale of the Textbox at 1, so that font-size values make sense. Otherwise
|
||||
* the same font-size value would result in different actual size depending
|
||||
* on the value of the scale.
|
||||
* @param {String} key
|
||||
* @param {Any} value
|
||||
*/
|
||||
setOnGroup: function (key, value) {
|
||||
if (key === 'scaleX') {
|
||||
this.set(key, Math.abs(1 / value));
|
||||
this.set('width', (this.get('width') * value) /
|
||||
(typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX));
|
||||
this.__oldScaleX = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns 2d representation (lineIndex and charIndex) of cursor (or selection start).
|
||||
* Overrides the superclass function to take into account text wrapping.
|
||||
* @param {Number} selectionStart Optional index. When not given, current selectionStart is used.
|
||||
* @returns {Object} This object has 'lineIndex' and 'charIndex' properties set to Numbers.
|
||||
*/
|
||||
get2DCursorLocation: function (selectionStart) {
|
||||
if (typeof selectionStart === 'undefined') {
|
||||
selectionStart = this.selectionStart;
|
||||
}
|
||||
|
||||
/*
|
||||
* We use `temp` to populate linesBeforeCursor instead of simply splitting
|
||||
* textBeforeCursor with newlines to handle the case of the
|
||||
* selectionStart value being on a word that, because of its length,
|
||||
* needs to be wrapped to the next line.
|
||||
*/
|
||||
var lineIndex = 0,
|
||||
linesBeforeCursor = [],
|
||||
allLines = this._textLines, temp = selectionStart;
|
||||
|
||||
while (temp >= 0) {
|
||||
if (lineIndex > allLines.length - 1) {
|
||||
break;
|
||||
}
|
||||
temp -= allLines[lineIndex].length;
|
||||
if (temp < 0) {
|
||||
linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex].slice(0,
|
||||
temp + allLines[lineIndex].length);
|
||||
}
|
||||
else {
|
||||
linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex];
|
||||
}
|
||||
lineIndex++;
|
||||
}
|
||||
lineIndex--;
|
||||
|
||||
var lastLine = linesBeforeCursor[linesBeforeCursor.length - 1],
|
||||
charIndex = lastLine.length;
|
||||
|
||||
if (linesBeforeCursor[lineIndex] === allLines[lineIndex]) {
|
||||
if (lineIndex + 1 < allLines.length - 1) {
|
||||
lineIndex++;
|
||||
charIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
lineIndex: lineIndex,
|
||||
charIndex: charIndex
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Overrides superclass function and uses text wrapping data to get cursor
|
||||
* boundary offsets instead of the array of chars.
|
||||
* @param {Array} chars Unused
|
||||
* @param {String} typeOfBoundaries Can be 'cursor' or 'selection'
|
||||
* @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set.
|
||||
*/
|
||||
_getCursorBoundariesOffsets: function (chars, typeOfBoundaries) {
|
||||
var topOffset = 0,
|
||||
leftOffset = 0,
|
||||
cursorLocation = this.get2DCursorLocation(),
|
||||
lineChars = this._textLines[cursorLocation.lineIndex].split(''),
|
||||
lineLeftOffset = this._getCachedLineOffset(cursorLocation.lineIndex);
|
||||
|
||||
for (var i = 0; i < cursorLocation.charIndex; i++) {
|
||||
leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i);
|
||||
}
|
||||
|
||||
for (i = 0; i < cursorLocation.lineIndex; i++) {
|
||||
topOffset += this._getHeightOfLine(this.ctx, i);
|
||||
}
|
||||
|
||||
if (typeOfBoundaries === 'cursor') {
|
||||
topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) / this.lineHeight
|
||||
- this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) * (1 - this._fontSizeFraction);
|
||||
}
|
||||
|
||||
return {
|
||||
top: topOffset,
|
||||
left: leftOffset,
|
||||
lineLeft: lineLeftOffset
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Returns object representation of an instance
|
||||
* @method toObject
|
||||
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
||||
* @return {Object} object representation of an instance
|
||||
*/
|
||||
toObject: function (propertiesToInclude) {
|
||||
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
|
||||
minWidth: this.minWidth
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Returns fabric.Textbox instance from an object representation
|
||||
* @static
|
||||
* @memberOf fabric.Textbox
|
||||
* @param {Object} object Object to create an instance from
|
||||
* @return {fabric.Textbox} instance of fabric.Textbox
|
||||
*/
|
||||
fabric.Textbox.fromObject = function (object) {
|
||||
return new fabric.Textbox(object.text, clone(object));
|
||||
};
|
||||
/**
|
||||
* Returns the default controls visibility required for Textboxes.
|
||||
* @returns {Object}
|
||||
*/
|
||||
fabric.Textbox.getTextboxControlVisibility = function () {
|
||||
return {
|
||||
tl: false,
|
||||
tr: false,
|
||||
br: false,
|
||||
bl: false,
|
||||
ml: true,
|
||||
mt: false,
|
||||
mr: true,
|
||||
mb: false,
|
||||
mtr: true
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Contains all fabric.Textbox objects that have been created
|
||||
* @static
|
||||
* @memberof fabric.Textbox
|
||||
* @type Array
|
||||
*/
|
||||
fabric.Textbox.instances = [];
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue