fabric.js/src/shapes/itext.class.js

989 lines
28 KiB
JavaScript
Raw Normal View History

2013-10-25 08:28:30 +00:00
(function() {
var clone = fabric.util.object.clone;
2013-10-25 08:28:30 +00:00
/**
2013-10-30 13:55:02 +00:00
* IText class (introduced in <b>v1.4</b>)
2013-10-25 08:28:30 +00:00
* @class fabric.IText
* @extends fabric.Text
* @mixes fabric.Observable
*
2013-10-30 13:55:02 +00:00
* @fires text:changed
* @fires editing:entered
* @fires editing:exited
*
2013-10-25 08:28:30 +00:00
* @return {fabric.IText} thisArg
* @see {@link fabric.IText#initialize} for constructor definition
*
2013-10-30 13:55:02 +00:00
* <p>Supported key combinations:</p>
* <pre>
2013-10-25 08:28:30 +00:00
* Move cursor: left, right, up, down
* Select character: shift + left, shift + right
* Select text vertically: shift + up, shift + down
* Move cursor by word: alt + left, alt + right
* Select words: shift + alt + left, shift + alt + right
* Move cursor to line start/end: cmd + left, cmd + right
* Select till start/end of line: cmd + shift + left, cmd + shift + right
* Jump to start/end of text: cmd + up, cmd + down
* Select till start/end of text: cmd + shift + up, cmd + shift + down
2013-10-26 16:45:17 +00:00
* Delete character: backspace
* Delete word: alt + backspace
* Delete line: cmd + backspace
* Forward delete: delete
2013-11-06 19:35:32 +00:00
* Copy text: ctrl/cmd + c
* Paste text: ctrl/cmd + v
* Cut text: ctrl/cmd + x
2013-10-30 13:55:02 +00:00
* </pre>
*
* <p>Supported mouse/touch combination</p>
* <pre>
* Position cursor: click/touch
* Create selection: click/touch & drag
* Create selection: click & shift + click
* Select word: double click
* Select line: triple click
* </pre>
2013-10-25 08:28:30 +00:00
*/
2013-10-30 13:55:02 +00:00
fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {
2013-10-25 08:28:30 +00:00
/**
* Type of an object
* @type String
* @default
*/
type: 'i-text',
/**
* Index where text selection starts (or where cursor is when there is no selection)
* @type Nubmer
* @default
*/
selectionStart: 0,
/**
* Index where text selection ends
* @type Nubmer
* @default
*/
selectionEnd: 0,
/**
* Color of text selection
* @type String
* @default
*/
selectionColor: 'rgba(17,119,255,0.3)',
/**
* Indicates whether text is in editing mode
* @type Boolean
* @default
*/
isEditing: false,
/**
* Indicates whether a text can be edited
* @type Boolean
* @default
*/
editable: true,
/**
* Border color of text object while it's in editing mode
* @type String
* @default
*/
editingBorderColor: 'rgba(102,153,255,0.25)',
/**
* Width of cursor (in px)
* @type Number
* @default
*/
cursorWidth: 2,
/**
* Color of default cursor (when not overwritten by character style)
* @type String
* @default
*/
cursorColor: '#333',
/**
* Delay between cursor blink (in ms)
* @type Number
* @default
*/
cursorDelay: 1000,
/**
* Duration of cursor fadein (in ms)
* @type Number
* @default
*/
cursorDuration: 600,
/**
* Object containing character styles
2013-10-25 08:56:24 +00:00
* (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
2013-10-25 08:28:30 +00:00
* @type Object
* @default
*/
styles: null,
skipFillStrokeCheck: true,
2013-10-27 13:35:25 +00:00
/**
* @private
*/
_reSpace: /\s|\n/,
2013-10-25 08:28:30 +00:00
/**
* @private
*/
_fontSizeFraction: 4,
/**
* @private
*/
_currentCursorOpacity: 0,
/**
* @private
*/
_selectionDirection: null,
/**
* @private
*/
_abortCursorAnimation: false,
/**
* @private
*/
_charWidthsCache: { },
2013-10-25 08:28:30 +00:00
/**
* Constructor
* @param {String} text Text string
* @param {Object} [options] Options object
* @return {fabric.IText} thisArg
*/
initialize: function(text, options) {
this.styles = options.styles || { };
this.callSuper('initialize', text, options);
this.initBehavior();
2013-11-01 21:39:28 +00:00
fabric.IText.instances.push(this);
2013-11-19 17:25:19 +00:00
// caching
this.__lineWidths = { };
this.__lineHeights = { };
this.__lineOffsets = { };
2013-10-25 08:28:30 +00:00
},
/**
* Returns true if object has no styling
2013-10-25 08:28:30 +00:00
*/
isEmptyStyles: function() {
if (!this.styles) return true;
var obj = this.styles;
2013-10-25 08:28:30 +00:00
for (var p1 in obj) {
for (var p2 in obj[p1]) {
/*jshint unused:false */
for (var p3 in obj[p1][p2]) {
return false;
}
2013-10-25 08:28:30 +00:00
}
}
return true;
2013-10-25 08:28:30 +00:00
},
/**
* Sets selection start (left boundary of a selection)
* @param {Number} index Index to set selection start to
*/
setSelectionStart: function(index) {
this.selectionStart = index;
this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index);
},
/**
* Sets selection end (right boundary of a selection)
* @param {Number} index Index to set selection end to
*/
setSelectionEnd: function(index) {
this.selectionEnd = index;
this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index);
},
/**
* Gets style of a current selection/cursor (at the start position)
* @return {Object} styles Style object at a cursor position
*/
getSelectionStyles: function() {
var loc = this.get2DCursorLocation();
if (this.styles[loc.lineIndex]) {
return this.styles[loc.lineIndex][loc.charIndex] || { };
}
return { };
},
/**
* Sets style of a current selection
* @param {Object} [styles] Styles object
* @return {fabric.IText} thisArg
* @chainable
*/
setSelectionStyles: function(styles) {
if (this.selectionStart === this.selectionEnd) {
2013-10-26 16:45:17 +00:00
this._extendStyles(this.selectionStart, styles);
2013-10-25 08:28:30 +00:00
}
else {
for (var i = this.selectionStart; i < this.selectionEnd; i++) {
this._extendStyles(i, styles);
2013-10-25 08:28:30 +00:00
}
}
return this;
2013-10-25 08:28:30 +00:00
},
/**
* @private
2013-10-25 08:28:30 +00:00
*/
_extendStyles: function(index, styles) {
var loc = this.get2DCursorLocation(index);
if (!this.styles[loc.lineIndex]) {
this.styles[loc.lineIndex] = { };
}
if (!this.styles[loc.lineIndex][loc.charIndex]) {
this.styles[loc.lineIndex][loc.charIndex] = { };
}
fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles);
2013-10-25 08:28:30 +00:00
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
this.callSuper('_render', ctx);
this.ctx = ctx;
2013-11-19 16:38:58 +00:00
this.isEditing && this.renderCursorOrSelection();
2013-10-25 08:28:30 +00:00
},
/**
* Renders cursor or selection (depending on what exists)
*/
2013-11-19 16:38:58 +00:00
renderCursorOrSelection: function() {
2013-10-25 08:28:30 +00:00
if (!this.active) return;
var chars = this.text.split(''),
boundaries;
if (this.selectionStart === this.selectionEnd) {
2013-11-19 16:38:58 +00:00
boundaries = this.getCursorBoundaries(chars, 'cursor');
this.renderCursor(boundaries);
2013-10-25 08:28:30 +00:00
}
else {
2013-11-19 16:38:58 +00:00
boundaries = this.getCursorBoundaries(chars, 'selection');
this.renderSelection(chars, boundaries);
2013-10-25 08:28:30 +00:00
}
},
/**
* Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
* @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
*/
get2DCursorLocation: function(selectionStart) {
if (typeof selectionStart === 'undefined') {
selectionStart = this.selectionStart;
}
var textBeforeCursor = this.text.slice(0, selectionStart);
var linesBeforeCursor = textBeforeCursor.split(this._reNewline);
return {
lineIndex: linesBeforeCursor.length - 1,
charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length
};
},
/**
* Returns fontSize of char at the current cursor
* @param {Number} lineIndex Line index
* @param {Number} charIndex Char index
* @return {Number} Character font size
*/
getCurrentCharFontSize: function(lineIndex, charIndex) {
return (
this.styles[lineIndex] &&
this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize;
},
/**
* Returns color (fill) of char at the current cursor
* @param {Number} lineIndex Line index
* @param {Number} charIndex Char index
* @return {String} Character color (fill)
*/
getCurrentCharColor: function(lineIndex, charIndex) {
return (
this.styles[lineIndex] &&
this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor;
},
/**
* Returns cursor boundaries (left, top, leftOffset, topOffset)
* @param {Array} chars Array of characters
2013-11-19 16:38:58 +00:00
* @param {String} typeOfBoundaries
2013-10-25 08:28:30 +00:00
*/
2013-11-19 16:38:58 +00:00
getCursorBoundaries: function(chars, typeOfBoundaries) {
2013-10-25 08:28:30 +00:00
2013-11-02 13:41:28 +00:00
var cursorLocation = this.get2DCursorLocation(),
2013-11-19 16:38:58 +00:00
2013-11-02 13:41:28 +00:00
textLines = this.text.split(this._reNewline),
2013-11-19 16:38:58 +00:00
// left/top are left/top of entire text box
// leftOffset/topOffset are offset from that left/top point of a text box
left = Math.round(this._getLeftOffset()),
top = -this.height / 2,
offsets = this._getCursorBoundariesOffsets(
chars, typeOfBoundaries, cursorLocation, textLines);
return {
left: left,
top: top,
leftOffset: offsets.left + offsets.lineLeft,
topOffset: offsets.top
};
},
2013-11-19 16:41:15 +00:00
/**
* @private
*/
2013-11-19 16:38:58 +00:00
_getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) {
2013-11-19 17:25:19 +00:00
var lineLeftOffset = 0,
2013-11-19 16:38:58 +00:00
2013-11-19 17:25:19 +00:00
lineIndex = 0,
charIndex = 0,
2013-10-25 08:28:30 +00:00
2013-11-19 16:38:58 +00:00
leftOffset = 0,
topOffset = typeOfBoundaries === 'cursor'
// selection starts at the very top of the line,
// whereas cursor starts at the padding created by line height
? (this._getHeightOfLine(this.ctx, 0) -
this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex))
: 0;
2013-10-25 08:28:30 +00:00
for (var i = 0; i < this.selectionStart; i++) {
if (chars[i] === '\n') {
leftOffset = 0;
var index = lineIndex + (typeOfBoundaries === 'cursor' ? 1 : 0);
2013-11-19 17:25:19 +00:00
topOffset += this._getCachedLineHeight(index);
2013-10-25 08:28:30 +00:00
lineIndex++;
charIndex = 0;
}
else {
2013-11-19 16:38:58 +00:00
leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
2013-10-25 08:28:30 +00:00
charIndex++;
}
2013-11-19 17:25:19 +00:00
lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines);
2013-10-25 08:28:30 +00:00
}
2013-11-19 17:25:19 +00:00
this._clearCache();
2013-10-25 08:28:30 +00:00
return {
2013-11-19 16:38:58 +00:00
top: topOffset,
left: leftOffset,
2013-11-19 17:25:19 +00:00
lineLeft: lineLeftOffset
2013-10-25 08:28:30 +00:00
};
},
2013-11-19 17:25:19 +00:00
/**
* @private
*/
_clearCache: function() {
this.__lineWidths = { };
this.__lineHeights = { };
this.__lineOffsets = { };
},
/**
* @private
*/
_getCachedLineHeight: function(index) {
return this.__lineHeights[index] ||
(this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index));
},
/**
* @private
*/
_getCachedLineWidth: function(lineIndex, textLines) {
return this.__lineWidths[lineIndex] ||
(this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines));
},
/**
* @private
*/
_getCachedLineOffset: function(lineIndex, textLines) {
var widthOfLine = this._getCachedLineWidth(lineIndex, textLines);
return this.__lineOffsets[lineIndex] ||
(this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine));
},
2013-10-25 08:28:30 +00:00
/**
* Renders cursor
2013-11-19 16:38:58 +00:00
* @param {Object} boundaries
2013-10-25 08:28:30 +00:00
*/
2013-11-19 16:38:58 +00:00
renderCursor: function(boundaries) {
var ctx = this.ctx;
2013-10-25 08:28:30 +00:00
ctx.save();
2013-11-20 11:47:03 +00:00
var cursorLocation = this.get2DCursorLocation(),
lineIndex = cursorLocation.lineIndex,
charIndex = cursorLocation.charIndex,
charHeight = this.getCurrentCharFontSize(lineIndex, charIndex);
2013-10-25 08:28:30 +00:00
ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
ctx.globalAlpha = this._currentCursorOpacity;
ctx.fillRect(
boundaries.left + boundaries.leftOffset,
boundaries.top + boundaries.topOffset,
2013-11-16 17:57:34 +00:00
this.cursorWidth / this.scaleX,
2013-10-25 08:28:30 +00:00
charHeight);
ctx.restore();
},
/**
* Renders text selection
* @param {Array} chars Array of characters
* @param {Object} boundaries Object with left/top/leftOffset/topOffset
*/
2013-11-19 16:38:58 +00:00
renderSelection: function(chars, boundaries) {
var ctx = this.ctx;
2013-10-25 08:28:30 +00:00
ctx.save();
ctx.fillStyle = this.selectionColor;
2013-11-20 11:47:03 +00:00
var cursorLocation = this.get2DCursorLocation(),
lineIndex = cursorLocation.lineIndex,
charIndex = cursorLocation.charIndex,
textLines = this.text.split(this._reNewline),
origLineIndex = lineIndex;
2013-10-25 08:28:30 +00:00
for (var i = this.selectionStart; i < this.selectionEnd; i++) {
if (chars[i] === '\n') {
boundaries.leftOffset = 0;
boundaries.topOffset += this._getHeightOfLine(ctx, lineIndex);
lineIndex++;
charIndex = 0;
}
else if (i !== this.text.length) {
2013-11-20 11:47:03 +00:00
var charWidth = this._getWidthOfChar(ctx, chars[i], lineIndex, charIndex),
lineOffset = this._getLineLeftOffset(this._getWidthOfLine(ctx, lineIndex, textLines)) || 0;
2013-10-25 08:28:30 +00:00
if (lineIndex === origLineIndex) {
2013-10-27 12:27:05 +00:00
// only offset the line if we're rendering selection of 2nd, 3rd, etc. line
2013-10-25 08:28:30 +00:00
lineOffset = 0;
}
ctx.fillRect(
boundaries.left + boundaries.leftOffset + lineOffset,
boundaries.top + boundaries.topOffset,
charWidth,
this._getHeightOfLine(ctx, lineIndex));
boundaries.leftOffset += charWidth;
charIndex++;
}
}
ctx.restore();
},
/**
* @private
* @param {String} method
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
2013-10-27 12:27:05 +00:00
_renderChars: function(method, ctx, line, left, top, lineIndex) {
2013-10-25 08:28:30 +00:00
if (this.isEmptyStyles()) {
2013-10-27 12:27:05 +00:00
return this._renderCharsFast(method, ctx, line, left, top);
2013-10-25 08:28:30 +00:00
}
this.skipTextAlign = true;
// set proper box offset
left -= this.textAlign === 'center'
? (this.width / 2)
: (this.textAlign === 'right')
? this.width
: 0;
// set proper line offset
2013-11-20 11:47:03 +00:00
var textLines = this.text.split(this._reNewline),
lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines),
lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines),
lineLeftOffset = this._getLineLeftOffset(lineWidth),
chars = line.split('');
2013-10-25 08:28:30 +00:00
left += lineLeftOffset || 0;
ctx.save();
for (var i = 0, len = chars.length; i < len; i++) {
2013-10-27 12:27:05 +00:00
this._renderChar(method, ctx, lineIndex, i, chars[i], left, top, lineHeight);
2013-10-25 08:56:24 +00:00
}
ctx.restore();
},
2013-10-25 08:28:30 +00:00
/**
* @private
* @param {String} method
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {String} line
*/
_renderCharsFast: function(method, ctx, line, left, top) {
this.skipTextAlign = false;
if (method === 'fillText' && this.fill) {
this.callSuper('_renderChars', method, ctx, line, left, top);
}
if (method === 'strokeText' && this.stroke) {
this.callSuper('_renderChars', method, ctx, line, left, top);
}
},
2013-10-25 08:56:24 +00:00
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
2013-10-27 12:27:05 +00:00
_renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
2013-10-25 08:56:24 +00:00
var decl, charWidth;
2013-10-25 08:28:30 +00:00
2013-10-25 08:56:24 +00:00
if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
2013-10-25 08:28:30 +00:00
2013-11-20 11:47:03 +00:00
var shouldStroke = decl.stroke || this.stroke,
shouldFill = decl.fill || this.fill;
2013-10-25 08:28:30 +00:00
2013-10-25 08:56:24 +00:00
ctx.save();
charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl);
2013-10-25 08:28:30 +00:00
2013-10-25 08:56:24 +00:00
if (shouldFill) {
ctx.fillText(_char, left, top);
}
if (shouldStroke) {
ctx.strokeText(_char, left, top);
2013-10-25 08:28:30 +00:00
}
2013-10-25 08:56:24 +00:00
this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight);
ctx.restore();
ctx.translate(charWidth, 0);
}
else {
if (method === 'strokeText' && this.stroke) {
ctx[method](_char, left, top);
2013-10-25 08:28:30 +00:00
}
2013-10-25 08:56:24 +00:00
if (method === 'fillText' && this.fill) {
ctx[method](_char, left, top);
}
charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight);
ctx.translate(ctx.measureText(_char).width, 0);
2013-10-25 08:28:30 +00:00
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight) {
var textDecoration = styleDeclaration
? (styleDeclaration.textDecoration || this.textDecoration)
: this.textDecoration;
if (!textDecoration) return;
if (textDecoration.indexOf('underline') > -1) {
this._renderCharDecorationAtOffset(
ctx,
left,
top + (this.fontSize / this._fontSizeFraction),
charWidth,
0
);
}
if (textDecoration.indexOf('line-through') > -1) {
this._renderCharDecorationAtOffset(
ctx,
left,
top + (this.fontSize / this._fontSizeFraction),
charWidth,
(lineHeight / this._fontSizeFraction)
);
}
if (textDecoration.indexOf('overline') > -1) {
this._renderCharDecorationAtOffset(
ctx,
left,
top,
charWidth,
lineHeight - (this.fontSize / this._fontSizeFraction)
);
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset) {
ctx.fillRect(left, top - offset, charWidth, 1);
},
/**
* @private
* @param {String} method
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {String} line
*/
_renderTextLine: function(method, ctx, line, left, top, lineIndex) {
// to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
top += this.fontSize / 4;
this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines
*/
_renderTextDecoration: function(ctx, textLines) {
if (this.isEmptyStyles()) {
return this.callSuper('_renderTextDecoration', ctx, textLines);
}
},
2013-10-25 08:28:30 +00:00
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextLinesBackground: function(ctx, textLines) {
if (!this.textBackgroundColor && !this.styles) return;
ctx.save();
if (this.textBackgroundColor) {
ctx.fillStyle = this.textBackgroundColor;
}
2013-11-20 11:47:03 +00:00
var lineHeights = 0,
fractionOfFontSize = this.fontSize / this._fontSizeFraction;
2013-10-25 08:28:30 +00:00
for (var i = 0, len = textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
if (textLines[i] === '') {
lineHeights += heightOfLine;
continue;
}
2013-11-20 11:47:03 +00:00
var lineWidth = this._getWidthOfLine(ctx, i, textLines),
lineLeftOffset = this._getLineLeftOffset(lineWidth);
2013-10-25 08:28:30 +00:00
if (this.textBackgroundColor) {
ctx.fillStyle = this.textBackgroundColor;
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset,
this._getTopOffset() + lineHeights + fractionOfFontSize,
lineWidth,
heightOfLine
);
}
if (this.styles[i]) {
for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) {
var _char = textLines[i][j];
ctx.fillStyle = this.styles[i][j].textBackgroundColor;
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines),
this._getTopOffset() + lineHeights + fractionOfFontSize,
this._getWidthOfChar(ctx, _char, i, j, textLines) + 1,
heightOfLine
);
}
}
}
lineHeights += heightOfLine;
}
ctx.restore();
},
/**
* @private
*/
_getCacheProp: function(_char, styleDeclaration) {
return _char +
styleDeclaration.fontFamily +
styleDeclaration.fontSize +
styleDeclaration.fontWeight +
styleDeclaration.fontStyle +
styleDeclaration.shadow;
},
2013-10-25 08:28:30 +00:00
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {String} _char
* @param {Number} lineIndex
* @param {Number} charIndex
* @param {Object} [decl]
2013-10-25 08:28:30 +00:00
*/
_applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
var styleDeclaration = decl || (this.styles[lineIndex] && this.styles[lineIndex][charIndex]);
if (styleDeclaration) {
// cloning so that original style object is not polluted with following font declarations
styleDeclaration = clone(styleDeclaration);
}
else {
styleDeclaration = { };
}
this._applyFontStyles(styleDeclaration);
var cacheProp = this._getCacheProp(_char, styleDeclaration);
2013-11-02 14:06:15 +00:00
// short-circuit if no styles
if (this.isEmptyStyles() && this._charWidthsCache[cacheProp]) {
return this._charWidthsCache[cacheProp];
}
if (typeof styleDeclaration.shadow === 'string') {
styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
}
var fill = styleDeclaration.fill || this.fill;
ctx.fillStyle = fill.toLive
? fill.toLive(ctx)
: fill;
if (styleDeclaration.stroke) {
ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
? styleDeclaration.stroke.toLive(ctx)
: styleDeclaration.stroke;
}
ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
ctx.font = this._getFontDeclaration.call(styleDeclaration);
this._setShadow.call(styleDeclaration, ctx);
2013-11-02 14:06:15 +00:00
if (!this._charWidthsCache[cacheProp]) {
this._charWidthsCache[cacheProp] = ctx.measureText(_char).width;
}
return this._charWidthsCache[cacheProp];
2013-10-25 08:28:30 +00:00
},
/**
* @private
* @param {Object} styleDeclaration
2013-10-25 08:28:30 +00:00
*/
_applyFontStyles: function(styleDeclaration) {
if (!styleDeclaration.fontFamily) {
styleDeclaration.fontFamily = this.fontFamily;
}
if (!styleDeclaration.fontSize) {
styleDeclaration.fontSize = this.fontSize;
}
if (!styleDeclaration.fontWeight) {
styleDeclaration.fontWeight = this.fontWeight;
}
if (!styleDeclaration.fontStyle) {
styleDeclaration.fontStyle = this.fontStyle;
2013-10-25 08:28:30 +00:00
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
ctx.save();
var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
ctx.restore();
return width;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getHeightOfChar: function(ctx, _char, lineIndex, charIndex) {
if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) {
return this.styles[lineIndex][charIndex].fontSize || this.fontSize;
}
return this.fontSize;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) {
lines = lines || this.text.split(this._reNewline);
var _char = lines[lineIndex].split('')[charIndex];
return this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) {
lines = lines || this.text.split(this._reNewline);
var _char = lines[lineIndex].split('')[charIndex];
return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) {
var width = 0;
for (var i = 0; i < charIndex; i++) {
width += this._getWidthOfCharAt(ctx, lineIndex, i, lines);
}
return width;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfLine: function(ctx, lineIndex, textLines) {
// if (!this.styles[lineIndex]) {
// return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]);
// }
return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getTextWidth: function(ctx, textLines) {
if (this.isEmptyStyles()) {
2013-10-25 08:28:30 +00:00
return this.callSuper('_getTextWidth', ctx, textLines);
}
var maxWidth = this._getWidthOfLine(ctx, 0, textLines);
for (var i = 1, len = textLines.length; i < len; i++) {
var currentLineWidth = this._getWidthOfLine(ctx, i, textLines);
if (currentLineWidth > maxWidth) {
maxWidth = currentLineWidth;
}
}
return maxWidth;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getHeightOfLine: function(ctx, lineIndex, textLines) {
textLines = textLines || this.text.split(this._reNewline);
var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0);
var line = textLines[lineIndex];
var chars = line.split('');
for (var i = 1, len = chars.length; i < len; i++) {
var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i);
if (currentCharHeight > maxHeight) {
maxHeight = currentCharHeight;
}
}
return maxHeight * this.lineHeight;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getTextHeight: function(ctx, textLines) {
var height = 0;
for (var i = 0, len = textLines.length; i < len; i++) {
height += this._getHeightOfLine(ctx, i, textLines);
}
return height;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getTopOffset: function() {
var topOffset = fabric.Text.prototype._getTopOffset.call(this);
return topOffset - (this.fontSize / this._fontSizeFraction);
},
/**
* Returns object representation of an instance
* @methd 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), {
styles: clone(this.styles)
});
2013-10-25 08:28:30 +00:00
}
});
/**
* Returns fabric.IText instance from an object representation
* @static
* @memberOf fabric.IText
* @param {Object} object Object to create an instance from
* @return {fabric.IText} instance of fabric.IText
*/
fabric.IText.fromObject = function(object) {
return new fabric.IText(object.text, clone(object));
};
2013-11-01 21:39:28 +00:00
fabric.IText.instances = [ ];
2013-10-25 08:28:30 +00:00
})();