2010-10-22 02:54:00 +00:00
|
|
|
(function(global) {
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-22 02:54:00 +00:00
|
|
|
"use strict";
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-22 02:54:00 +00:00
|
|
|
var fabric = global.fabric || (global.fabric = { }),
|
2010-07-26 23:20:19 +00:00
|
|
|
extend = fabric.util.object.extend,
|
2012-02-01 23:43:52 +00:00
|
|
|
clone = fabric.util.object.clone,
|
|
|
|
|
toFixed = fabric.util.toFixed;
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2012-01-18 11:00:59 +00:00
|
|
|
if (fabric.Text) {
|
2010-10-11 18:45:06 +00:00
|
|
|
fabric.warn('fabric.Text is already defined');
|
2010-06-09 22:34:55 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2010-07-09 23:43:50 +00:00
|
|
|
if (!fabric.Object) {
|
2010-10-11 18:45:06 +00:00
|
|
|
fabric.warn('fabric.Text requires fabric.Object');
|
2010-06-09 22:34:55 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2012-01-18 11:00:59 +00:00
|
|
|
|
|
|
|
|
/**
|
2010-10-14 21:42:39 +00:00
|
|
|
* @class Text
|
|
|
|
|
* @extends fabric.Object
|
|
|
|
|
*/
|
|
|
|
|
fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ {
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type Number
|
|
|
|
|
*/
|
2011-09-21 15:18:58 +00:00
|
|
|
fontSize: 40,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type Number
|
|
|
|
|
*/
|
2011-07-22 00:32:02 +00:00
|
|
|
fontWeight: 100,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
2012-07-26 22:26:54 +00:00
|
|
|
fontFamily: 'Times New Roman',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
textDecoration: '',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String | null
|
|
|
|
|
*/
|
2012-07-24 09:21:32 +00:00
|
|
|
textShadow: '',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* Determines text alignment. Possible values: "left", "center", or "right".
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
textAlign: 'left',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
fontStyle: '',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type Number
|
|
|
|
|
*/
|
2012-07-24 09:21:32 +00:00
|
|
|
lineHeight: 1.3,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
strokeStyle: '',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type Number
|
|
|
|
|
*/
|
|
|
|
|
strokeWidth: 1,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
|
|
|
|
backgroundColor: '',
|
2012-01-18 11:00:59 +00:00
|
|
|
|
|
|
|
|
|
2011-07-21 18:53:48 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String | null
|
|
|
|
|
*/
|
|
|
|
|
path: null,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-15 02:16:24 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
2011-07-21 18:53:48 +00:00
|
|
|
type: 'text',
|
2012-07-24 09:21:32 +00:00
|
|
|
|
2012-07-14 20:35:45 +00:00
|
|
|
/**
|
2012-07-24 09:21:32 +00:00
|
|
|
* Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used)
|
2012-07-14 20:35:45 +00:00
|
|
|
* @property
|
|
|
|
|
* @type Boolean
|
|
|
|
|
*/
|
|
|
|
|
useNative: true,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
* @method initialize
|
|
|
|
|
* @param {String} text
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
* @return {fabric.Text} thisArg
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
initialize: function(text, options) {
|
2010-10-19 20:27:24 +00:00
|
|
|
this._initStateProperties();
|
2010-06-09 22:34:55 +00:00
|
|
|
this.text = text;
|
2012-08-11 16:59:53 +00:00
|
|
|
this.setOptions(options || { });
|
2011-02-09 23:21:45 +00:00
|
|
|
this.theta = this.angle * Math.PI / 180;
|
2012-07-27 00:56:52 +00:00
|
|
|
this._initDimensions();
|
2010-06-09 22:34:55 +00:00
|
|
|
this.setCoords();
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-07-27 00:56:52 +00:00
|
|
|
/**
|
|
|
|
|
* Renders text object on offscreen canvas, so that it would get dimensions
|
|
|
|
|
* @private
|
|
|
|
|
* @method _initDimensions
|
|
|
|
|
*/
|
|
|
|
|
_initDimensions: function() {
|
|
|
|
|
var canvasEl = fabric.document.createElement('canvas');
|
|
|
|
|
|
|
|
|
|
if (!canvasEl.getContext && typeof G_vmlCanvasManager != 'undefined') {
|
|
|
|
|
G_vmlCanvasManager.initElement(canvasEl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._render(canvasEl.getContext('2d'));
|
|
|
|
|
},
|
|
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
2012-01-18 11:00:59 +00:00
|
|
|
* Creates `stateProperties` list on an instance, and adds `fabric.Text` -specific ones to it
|
2011-07-22 00:32:02 +00:00
|
|
|
* (such as "fontFamily", "fontWeight", etc.)
|
2010-10-19 20:27:24 +00:00
|
|
|
* @private
|
2011-03-02 00:08:38 +00:00
|
|
|
* @method _initStateProperties
|
2010-10-14 21:42:39 +00:00
|
|
|
*/
|
2010-10-19 20:27:24 +00:00
|
|
|
_initStateProperties: function() {
|
2011-03-02 00:08:38 +00:00
|
|
|
this.stateProperties = this.stateProperties.concat();
|
|
|
|
|
this.stateProperties.push(
|
2012-01-18 11:00:59 +00:00
|
|
|
'fontFamily',
|
|
|
|
|
'fontWeight',
|
|
|
|
|
'fontSize',
|
|
|
|
|
'path',
|
|
|
|
|
'text',
|
|
|
|
|
'textDecoration',
|
|
|
|
|
'textShadow',
|
2011-07-21 18:53:48 +00:00
|
|
|
'textAlign',
|
2011-03-28 22:57:40 +00:00
|
|
|
'fontStyle',
|
2011-04-20 20:36:31 +00:00
|
|
|
'lineHeight',
|
2011-03-28 22:57:40 +00:00
|
|
|
'strokeStyle',
|
2011-05-13 18:34:24 +00:00
|
|
|
'strokeWidth',
|
|
|
|
|
'backgroundColor'
|
2011-03-02 00:08:38 +00:00
|
|
|
);
|
|
|
|
|
fabric.util.removeFromArray(this.stateProperties, 'width');
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* Returns string representation of an instance
|
|
|
|
|
* @method toString
|
|
|
|
|
* @return {String} String representation of text object
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
toString: function() {
|
2011-07-15 22:16:14 +00:00
|
|
|
return '#<fabric.Text (' + this.complexity() +
|
2011-07-22 00:32:02 +00:00
|
|
|
'): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _render
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
|
|
|
*/
|
2011-08-02 21:50:13 +00:00
|
|
|
_render: function(ctx) {
|
2012-07-14 20:35:45 +00:00
|
|
|
if (typeof Cufon === 'undefined' || this.useNative === true) {
|
2012-07-24 09:21:32 +00:00
|
|
|
this._renderViaNative(ctx);
|
2012-07-14 20:35:45 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2012-07-24 09:21:32 +00:00
|
|
|
this._renderViaCufon(ctx);
|
2012-07-14 20:35:45 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _renderViaCufon
|
|
|
|
|
*/
|
|
|
|
|
_renderViaCufon: function(ctx) {
|
|
|
|
|
var o = Cufon.textOptions || (Cufon.textOptions = { });
|
|
|
|
|
|
|
|
|
|
// export options to be used by cufon.js
|
|
|
|
|
o.left = this.left;
|
|
|
|
|
o.top = this.top;
|
|
|
|
|
o.context = ctx;
|
|
|
|
|
o.color = this.fill;
|
|
|
|
|
|
|
|
|
|
var el = this._initDummyElementForCufon();
|
|
|
|
|
|
|
|
|
|
// set "cursor" to top/left corner
|
|
|
|
|
this.transform(ctx);
|
|
|
|
|
|
|
|
|
|
// draw text
|
|
|
|
|
Cufon.replaceElement(el, {
|
|
|
|
|
engine: 'canvas',
|
|
|
|
|
separate: 'none',
|
|
|
|
|
fontFamily: this.fontFamily,
|
|
|
|
|
fontWeight: this.fontWeight,
|
|
|
|
|
textDecoration: this.textDecoration,
|
|
|
|
|
textShadow: this.textShadow,
|
|
|
|
|
textAlign: this.textAlign,
|
|
|
|
|
fontStyle: this.fontStyle,
|
|
|
|
|
lineHeight: this.lineHeight,
|
|
|
|
|
strokeStyle: this.strokeStyle,
|
|
|
|
|
strokeWidth: this.strokeWidth,
|
|
|
|
|
backgroundColor: this.backgroundColor
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// update width, height
|
|
|
|
|
this.width = o.width;
|
|
|
|
|
this.height = o.height;
|
|
|
|
|
|
|
|
|
|
this._totalLineHeight = o.totalLineHeight;
|
|
|
|
|
this._fontAscent = o.fontAscent;
|
|
|
|
|
this._boundaries = o.boundaries;
|
|
|
|
|
this._shadowOffsets = o.shadowOffsets;
|
|
|
|
|
this._shadows = o.shadows || [ ];
|
|
|
|
|
|
|
|
|
|
// need to set coords _after_ the width/height was retreived from Cufon
|
|
|
|
|
this.setCoords();
|
|
|
|
|
},
|
|
|
|
|
|
2012-07-14 20:35:45 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _render_native
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
|
|
|
*/
|
2012-07-24 09:21:32 +00:00
|
|
|
_renderViaNative: function(ctx) {
|
|
|
|
|
|
|
|
|
|
this.transform(ctx);
|
|
|
|
|
this._setTextStyles(ctx);
|
|
|
|
|
|
|
|
|
|
var textLines = this.text.split(/\r?\n/);
|
|
|
|
|
|
|
|
|
|
this.width = this._getTextWidth(ctx, textLines);
|
|
|
|
|
this.height = this._getTextHeight(ctx, textLines);
|
|
|
|
|
|
|
|
|
|
this._renderTextBackground(ctx, textLines);
|
|
|
|
|
|
|
|
|
|
if (this.textAlign !== 'left') {
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._setTextShadow(ctx);
|
|
|
|
|
this._renderTextFill(ctx, textLines);
|
|
|
|
|
this.textShadow && ctx.restore();
|
|
|
|
|
|
|
|
|
|
this._renderTextStroke(ctx, textLines);
|
|
|
|
|
if (this.textAlign !== 'left') {
|
|
|
|
|
ctx.restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._renderTextDecoration(ctx, textLines);
|
2012-07-26 22:26:54 +00:00
|
|
|
this._setBoundaries(ctx, textLines);
|
|
|
|
|
this._totalLineHeight = 0;
|
2012-07-24 09:21:32 +00:00
|
|
|
|
|
|
|
|
this.setCoords();
|
|
|
|
|
},
|
2012-07-14 20:35:45 +00:00
|
|
|
|
2012-07-26 22:26:54 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _setBoundaries
|
|
|
|
|
*/
|
|
|
|
|
_setBoundaries: function(ctx, textLines) {
|
|
|
|
|
this._boundaries = [ ];
|
|
|
|
|
|
|
|
|
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
|
|
|
|
|
var lineWidth = ctx.measureText(textLines[i]).width;
|
|
|
|
|
var lineLeftOffset = this._getLineLeftOffset(lineWidth);
|
|
|
|
|
|
|
|
|
|
this._boundaries.push({
|
|
|
|
|
height: this.fontSize,
|
|
|
|
|
width: lineWidth,
|
|
|
|
|
left: lineLeftOffset
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _setTextStyles
|
|
|
|
|
*/
|
|
|
|
|
_setTextStyles: function(ctx) {
|
2012-07-14 20:35:45 +00:00
|
|
|
ctx.fillStyle = this.fill;
|
|
|
|
|
ctx.strokeStyle = this.strokeStyle;
|
|
|
|
|
ctx.lineWidth = this.strokeWidth;
|
|
|
|
|
ctx.textBaseline = 'top';
|
|
|
|
|
ctx.textAlign = this.textAlign;
|
2012-07-24 09:21:32 +00:00
|
|
|
ctx.font = this._getFontDeclaration();
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _getTextHeight
|
|
|
|
|
*/
|
|
|
|
|
_getTextHeight: function(ctx, textLines) {
|
|
|
|
|
return this.fontSize * textLines.length * this.lineHeight;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _getTextWidth
|
|
|
|
|
*/
|
|
|
|
|
_getTextWidth: function(ctx, textLines) {
|
|
|
|
|
var maxWidth = ctx.measureText(textLines[0]).width;
|
2012-07-14 20:35:45 +00:00
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
for (var i = 1, len = textLines.length; i < len; i++) {
|
|
|
|
|
var currentLineWidth = ctx.measureText(textLines[i]).width;
|
|
|
|
|
if (currentLineWidth > maxWidth) {
|
|
|
|
|
maxWidth = currentLineWidth;
|
|
|
|
|
}
|
2012-07-14 20:35:45 +00:00
|
|
|
}
|
2012-07-24 09:21:32 +00:00
|
|
|
return maxWidth;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _setTextShadow
|
|
|
|
|
*/
|
|
|
|
|
_setTextShadow: function(ctx) {
|
|
|
|
|
if (this.textShadow) {
|
|
|
|
|
|
|
|
|
|
// "rgba(0,0,0,0.2) 2px 2px 10px"
|
|
|
|
|
// "rgb(0, 100, 0) 0 0 5px"
|
|
|
|
|
// "red 2px 2px 1px"
|
|
|
|
|
// "#f55 123 345 567"
|
2012-07-28 15:58:45 +00:00
|
|
|
var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/;
|
2012-07-24 09:21:32 +00:00
|
|
|
|
|
|
|
|
var shadowDeclaration = this.textShadow;
|
|
|
|
|
var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow);
|
|
|
|
|
var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, '');
|
|
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.shadowColor = shadowColor;
|
2012-07-29 07:53:40 +00:00
|
|
|
ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10);
|
|
|
|
|
ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10);
|
|
|
|
|
ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10);
|
2012-07-26 22:26:54 +00:00
|
|
|
|
|
|
|
|
this._shadows = [{
|
|
|
|
|
blur: ctx.shadowBlur,
|
|
|
|
|
color: ctx.shadowColor,
|
|
|
|
|
offX: ctx.shadowOffsetX,
|
|
|
|
|
offY: ctx.shadowOffsetY
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
this._shadowOffsets = [[
|
|
|
|
|
parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10)
|
|
|
|
|
]];
|
2012-07-24 09:21:32 +00:00
|
|
|
}
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
_renderTextFill: function(ctx, textLines) {
|
2012-07-26 22:26:54 +00:00
|
|
|
this._boundaries = [ ];
|
2012-07-24 09:21:32 +00:00
|
|
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
ctx.fillText(
|
|
|
|
|
textLines[i],
|
|
|
|
|
-this.width / 2,
|
|
|
|
|
(-this.height / 2) + (i * this.fontSize * this.lineHeight)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _renderTextStroke
|
|
|
|
|
*/
|
|
|
|
|
_renderTextStroke: function(ctx, textLines) {
|
|
|
|
|
if (this.strokeStyle) {
|
|
|
|
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
ctx.strokeText(
|
|
|
|
|
textLines[i],
|
|
|
|
|
-this.width / 2,
|
|
|
|
|
(-this.height / 2) + (i * this.fontSize * this.lineHeight)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @_renderTextBackground
|
|
|
|
|
*/
|
|
|
|
|
_renderTextBackground: function(ctx, textLines) {
|
|
|
|
|
if (this.backgroundColor) {
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.fillStyle = this.backgroundColor;
|
|
|
|
|
|
|
|
|
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
|
|
|
|
|
var lineWidth = ctx.measureText(textLines[i]).width;
|
|
|
|
|
var lineLeftOffset = this._getLineLeftOffset(lineWidth);
|
|
|
|
|
|
|
|
|
|
ctx.fillRect(
|
|
|
|
|
(-this.width / 2) + lineLeftOffset,
|
|
|
|
|
(-this.height / 2) + (i * this.fontSize * this.lineHeight),
|
|
|
|
|
lineWidth,
|
|
|
|
|
this.fontSize
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
ctx.restore();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _getLineLeftOffset
|
|
|
|
|
*/
|
|
|
|
|
_getLineLeftOffset: function(lineWidth) {
|
|
|
|
|
if (this.textAlign === 'center') {
|
|
|
|
|
return (this.width - lineWidth) / 2;
|
|
|
|
|
}
|
|
|
|
|
if (this.textAlign === 'right') {
|
|
|
|
|
return this.width - lineWidth;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _renderTextDecoration
|
|
|
|
|
*/
|
|
|
|
|
_renderTextDecoration: function(ctx, textLines) {
|
|
|
|
|
|
|
|
|
|
var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2;
|
|
|
|
|
|
|
|
|
|
function renderLinesAtOffset(offset) {
|
|
|
|
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
|
|
|
|
|
var lineWidth = ctx.measureText(textLines[i]).width;
|
|
|
|
|
var lineLeftOffset = this._getLineLeftOffset(lineWidth);
|
|
|
|
|
|
|
|
|
|
ctx.fillRect(
|
|
|
|
|
(-this.width / 2) + lineLeftOffset,
|
|
|
|
|
(offset + (i * this.fontSize * this.lineHeight)) - halfOfVerticalBox,
|
|
|
|
|
lineWidth,
|
|
|
|
|
1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.textDecoration.indexOf('underline') > -1) {
|
|
|
|
|
renderLinesAtOffset.call(this, this.fontSize);
|
|
|
|
|
}
|
|
|
|
|
if (this.textDecoration.indexOf('line-through') > -1) {
|
|
|
|
|
renderLinesAtOffset.call(this, this.fontSize / 2);
|
|
|
|
|
}
|
|
|
|
|
if (this.textDecoration.indexOf('overline') > -1) {
|
|
|
|
|
renderLinesAtOffset.call(this, 0);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _getFontDeclaration
|
|
|
|
|
*/
|
|
|
|
|
_getFontDeclaration: function() {
|
|
|
|
|
return [
|
|
|
|
|
this.fontStyle,
|
|
|
|
|
this.fontWeight,
|
|
|
|
|
this.fontSize + 'px',
|
|
|
|
|
this.fontFamily
|
|
|
|
|
].join(' ');
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _initDummyElement
|
|
|
|
|
*/
|
2012-07-24 09:21:32 +00:00
|
|
|
_initDummyElementForCufon: function() {
|
2012-01-18 14:12:57 +00:00
|
|
|
var el = fabric.document.createElement('pre'),
|
2011-08-11 19:18:18 +00:00
|
|
|
container = fabric.document.createElement('div');
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-02-02 00:57:01 +00:00
|
|
|
// Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent
|
|
|
|
|
container.appendChild(el);
|
2012-01-18 14:12:57 +00:00
|
|
|
|
|
|
|
|
if (typeof G_vmlCanvasManager == 'undefined') {
|
|
|
|
|
el.innerHTML = this.text;
|
2012-01-27 00:39:30 +00:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// IE 7 & 8 drop newlines and white space on text nodes
|
|
|
|
|
// see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
|
|
|
|
|
// see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp
|
2012-01-18 14:12:57 +00:00
|
|
|
el.innerText = this.text.replace(/\r?\n/gi, '\r');
|
|
|
|
|
}
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-09-21 15:18:58 +00:00
|
|
|
el.style.fontSize = this.fontSize + 'px';
|
2010-06-09 22:34:55 +00:00
|
|
|
el.style.letterSpacing = 'normal';
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
return el;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
2010-10-19 20:27:24 +00:00
|
|
|
* Renders text instance on a specified context
|
2010-10-14 21:42:39 +00:00
|
|
|
* @method render
|
|
|
|
|
* @param ctx {CanvasRenderingContext2D} context to render on
|
|
|
|
|
*/
|
2011-08-02 21:50:13 +00:00
|
|
|
render: function(ctx, noTransform) {
|
|
|
|
|
ctx.save();
|
|
|
|
|
this._render(ctx);
|
|
|
|
|
if (!noTransform && this.active) {
|
|
|
|
|
this.drawBorders(ctx);
|
|
|
|
|
this.hideCorners || this.drawCorners(ctx);
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
2011-08-02 21:50:13 +00:00
|
|
|
ctx.restore();
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Returns object representation of an instance
|
|
|
|
|
* @method toObject
|
|
|
|
|
* @return {Object} Object representation of text object
|
|
|
|
|
*/
|
|
|
|
|
toObject: function() {
|
|
|
|
|
return extend(this.callSuper('toObject'), {
|
|
|
|
|
text: this.text,
|
2011-07-22 00:32:02 +00:00
|
|
|
fontSize: this.fontSize,
|
|
|
|
|
fontWeight: this.fontWeight,
|
|
|
|
|
fontFamily: this.fontFamily,
|
2011-04-23 21:39:56 +00:00
|
|
|
fontStyle: this.fontStyle,
|
|
|
|
|
lineHeight: this.lineHeight,
|
|
|
|
|
textDecoration: this.textDecoration,
|
|
|
|
|
textShadow: this.textShadow,
|
2011-07-21 18:53:48 +00:00
|
|
|
textAlign: this.textAlign,
|
2011-04-23 21:39:56 +00:00
|
|
|
path: this.path,
|
|
|
|
|
strokeStyle: this.strokeStyle,
|
2011-05-13 18:34:24 +00:00
|
|
|
strokeWidth: this.strokeWidth,
|
|
|
|
|
backgroundColor: this.backgroundColor
|
2011-04-23 21:39:56 +00:00
|
|
|
});
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-02 21:14:20 +00:00
|
|
|
/**
|
|
|
|
|
* Returns svg representation of an instance
|
|
|
|
|
* @method toSVG
|
|
|
|
|
* @return {string} svg representation of an instance
|
|
|
|
|
*/
|
|
|
|
|
toSVG: function() {
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-02-03 16:47:48 +00:00
|
|
|
var textLines = this.text.split(/\r?\n/),
|
2012-07-26 22:26:54 +00:00
|
|
|
lineTopOffset = this.useNative
|
|
|
|
|
? this.fontSize * this.lineHeight
|
|
|
|
|
: (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)),
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
textLeftOffset = -(this.width/2),
|
2012-07-26 22:26:54 +00:00
|
|
|
textTopOffset = this.useNative
|
|
|
|
|
? this.fontSize - 1
|
|
|
|
|
: (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight,
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
textAndBg = this._getSVGTextAndBg(lineTopOffset, textLeftOffset, textLines),
|
|
|
|
|
shadowSpans = this._getSVGShadows(lineTopOffset, textLines);
|
2012-06-26 14:42:45 +00:00
|
|
|
|
2012-02-10 05:37:06 +00:00
|
|
|
// move top offset by an ascent
|
2012-07-26 22:26:54 +00:00
|
|
|
textTopOffset += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0);
|
2012-06-26 14:42:45 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
return [
|
|
|
|
|
'<g transform="', this.getSvgTransform(), '">',
|
|
|
|
|
textAndBg.textBgRects.join(''),
|
|
|
|
|
'<text ',
|
|
|
|
|
(this.fontFamily ? 'font-family="\'' + this.fontFamily + '\'" ': ''),
|
|
|
|
|
(this.fontSize ? 'font-size="' + this.fontSize + '" ': ''),
|
|
|
|
|
(this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''),
|
|
|
|
|
(this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''),
|
|
|
|
|
(this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''),
|
|
|
|
|
'style="', this.getSvgStyles(), '" ',
|
|
|
|
|
/* svg starts from left/bottom corner so we normalize height */
|
2012-02-01 23:43:52 +00:00
|
|
|
'transform="translate(', toFixed(textLeftOffset, 2), ' ', toFixed(textTopOffset, 2), ')">',
|
2012-01-07 00:58:21 +00:00
|
|
|
shadowSpans.join(''),
|
|
|
|
|
textAndBg.textSpans.join(''),
|
|
|
|
|
'</text>',
|
|
|
|
|
'</g>'
|
|
|
|
|
].join('');
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
_getSVGShadows: function(lineTopOffset, textLines) {
|
2012-02-03 16:47:48 +00:00
|
|
|
var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1;
|
|
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
if (!this._shadows || !this._boundaries) {
|
|
|
|
|
return shadowSpans;
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-03 16:47:48 +00:00
|
|
|
for (j = 0, jlen = this._shadows.length; j < jlen; j++) {
|
|
|
|
|
for (i = 0, ilen = textLines.length; i < ilen; i++) {
|
|
|
|
|
if (textLines[i] !== '') {
|
|
|
|
|
var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0;
|
|
|
|
|
shadowSpans.push(
|
|
|
|
|
'<tspan x="',
|
|
|
|
|
toFixed((lineLeftOffset + lineTopOffsetMultiplier) + this._shadowOffsets[j][0], 2),
|
2012-07-26 22:26:54 +00:00
|
|
|
((i === 0 || this.useNative) ? '" y' : '" dy'), '="',
|
|
|
|
|
toFixed(this.useNative
|
|
|
|
|
? ((lineTopOffset * i) - this.height / 2 + this._shadowOffsets[j][1])
|
|
|
|
|
: (lineTopOffset + (i === 0 ? this._shadowOffsets[j][1] : 0)), 2),
|
2012-02-03 16:47:48 +00:00
|
|
|
'" ',
|
|
|
|
|
this._getFillAttributes(this._shadows[j].color), '>',
|
2012-02-09 08:54:30 +00:00
|
|
|
fabric.util.string.escapeXml(textLines[i]),
|
2012-02-03 16:47:48 +00:00
|
|
|
'</tspan>');
|
|
|
|
|
lineTopOffsetMultiplier = 1;
|
|
|
|
|
} else {
|
|
|
|
|
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
|
|
|
|
|
// prevents empty tspans
|
|
|
|
|
lineTopOffsetMultiplier++;
|
|
|
|
|
}
|
2012-01-07 00:58:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return shadowSpans;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
_getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) {
|
2012-02-03 16:47:48 +00:00
|
|
|
var textSpans = [ ], textBgRects = [ ], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1;
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-01-07 00:58:21 +00:00
|
|
|
// text and background
|
2012-02-03 16:47:48 +00:00
|
|
|
for (i = 0, len = textLines.length; i < len; i++) {
|
|
|
|
|
if (textLines[i] !== '') {
|
|
|
|
|
lineLeftOffset = (this._boundaries && this._boundaries[i]) ? toFixed(this._boundaries[i].left, 2) : 0;
|
|
|
|
|
textSpans.push(
|
|
|
|
|
'<tspan x="',
|
|
|
|
|
lineLeftOffset, '" ',
|
2012-07-26 22:26:54 +00:00
|
|
|
(i === 0 || this.useNative ? 'y' : 'dy'), '="',
|
|
|
|
|
toFixed(this.useNative ? ((lineTopOffset * i) - this.height / 2) : (lineTopOffset * lineTopOffsetMultiplier), 2) , '" ',
|
2012-02-03 16:47:48 +00:00
|
|
|
// doing this on <tspan> elements since setting opacity on containing <text> one doesn't work in Illustrator
|
|
|
|
|
this._getFillAttributes(this.fill), '>',
|
2012-02-09 08:54:30 +00:00
|
|
|
fabric.util.string.escapeXml(textLines[i]),
|
2012-02-03 16:47:48 +00:00
|
|
|
'</tspan>'
|
|
|
|
|
);
|
|
|
|
|
lineTopOffsetMultiplier = 1;
|
|
|
|
|
} else {
|
|
|
|
|
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
|
|
|
|
|
// prevents empty tspans
|
|
|
|
|
lineTopOffsetMultiplier++;
|
|
|
|
|
}
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-07-24 09:21:32 +00:00
|
|
|
if (!this.backgroundColor || !this._boundaries) continue;
|
2012-02-03 16:47:48 +00:00
|
|
|
|
2012-01-04 17:31:37 +00:00
|
|
|
textBgRects.push(
|
2012-02-01 23:43:52 +00:00
|
|
|
'<rect ',
|
|
|
|
|
this._getFillAttributes(this.backgroundColor),
|
|
|
|
|
' x="',
|
|
|
|
|
toFixed(textLeftOffset + this._boundaries[i].left, 2),
|
2012-01-18 11:00:59 +00:00
|
|
|
'" y="',
|
2012-01-07 00:58:21 +00:00
|
|
|
/* an offset that seems to straighten things out */
|
2012-02-10 05:37:06 +00:00
|
|
|
toFixed((lineTopOffset * i) - this.height / 2, 2),
|
2012-01-18 11:00:59 +00:00
|
|
|
'" width="',
|
2012-02-01 23:43:52 +00:00
|
|
|
toFixed(this._boundaries[i].width, 2),
|
2012-01-18 11:00:59 +00:00
|
|
|
'" height="',
|
2012-02-01 23:43:52 +00:00
|
|
|
toFixed(this._boundaries[i].height, 2),
|
2012-01-04 17:31:37 +00:00
|
|
|
'"></rect>');
|
2012-01-02 21:14:20 +00:00
|
|
|
}
|
2012-01-07 00:58:21 +00:00
|
|
|
return {
|
|
|
|
|
textSpans: textSpans,
|
|
|
|
|
textBgRects: textBgRects
|
|
|
|
|
};
|
2012-01-02 21:14:20 +00:00
|
|
|
},
|
2012-02-03 16:47:48 +00:00
|
|
|
|
2012-02-01 23:43:52 +00:00
|
|
|
// Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
|
|
|
|
|
// we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
|
|
|
|
|
_getFillAttributes: function(value) {
|
|
|
|
|
var fillColor = value ? new fabric.Color(value) : '';
|
|
|
|
|
if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
|
|
|
|
|
return 'fill="' + value + '"';
|
|
|
|
|
}
|
|
|
|
|
return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
|
|
|
|
|
},
|
2012-01-07 00:58:21 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Sets "color" of an instance (alias of `set('fill', …)`)
|
|
|
|
|
* @method setColor
|
|
|
|
|
* @param {String} value
|
|
|
|
|
* @return {fabric.Text} thisArg
|
|
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setColor: function(value) {
|
|
|
|
|
this.set('fill', value);
|
|
|
|
|
return this;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
2011-07-22 00:32:02 +00:00
|
|
|
* Sets fontSize of an instance and updates its coordinates
|
2011-04-23 21:39:56 +00:00
|
|
|
* @method setFontsize
|
|
|
|
|
* @param {Number} value
|
|
|
|
|
* @return {fabric.Text} thisArg
|
|
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setFontsize: function(value) {
|
2011-07-22 00:32:02 +00:00
|
|
|
this.set('fontSize', value);
|
2011-04-23 21:39:56 +00:00
|
|
|
this.setCoords();
|
|
|
|
|
return this;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Returns actual text value of an instance
|
|
|
|
|
* @method getText
|
|
|
|
|
* @return {String}
|
|
|
|
|
*/
|
|
|
|
|
getText: function() {
|
|
|
|
|
return this.text;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Sets text of an instance, and updates its coordinates
|
|
|
|
|
* @method setText
|
|
|
|
|
* @param {String} value
|
|
|
|
|
* @return {fabric.Text} thisArg
|
|
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setText: function(value) {
|
|
|
|
|
this.set('text', value);
|
|
|
|
|
this.setCoords();
|
|
|
|
|
return this;
|
|
|
|
|
},
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Sets specified property to a specified value
|
|
|
|
|
* @method set
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* @param {Any} value
|
|
|
|
|
* @return {fabric.Text} thisArg
|
|
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
set: function(name, value) {
|
2011-08-12 17:46:17 +00:00
|
|
|
if (typeof name == 'object') {
|
|
|
|
|
for (var prop in name) {
|
|
|
|
|
this.set(prop, name[prop]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this[name] = value;
|
|
|
|
|
if (name === 'fontFamily' && this.path) {
|
|
|
|
|
this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3');
|
|
|
|
|
}
|
2011-04-23 21:39:56 +00:00
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
}
|
2010-06-09 22:34:55 +00:00
|
|
|
});
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2012-08-11 16:59:53 +00:00
|
|
|
/**
|
|
|
|
|
* List of attribute names to account for when parsing SVG element (used by `fabric.Text.fromElement`)
|
|
|
|
|
* @static
|
|
|
|
|
*/
|
|
|
|
|
fabric.Text.ATTRIBUTE_NAMES =
|
|
|
|
|
('x y fill fill-opacity opacity stroke stroke-width transform ' +
|
|
|
|
|
'font-family font-style font-weight font-size text-decoration').split(' ');
|
|
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Returns fabric.Text instance from an object representation
|
2010-06-09 22:34:55 +00:00
|
|
|
* @static
|
|
|
|
|
* @method fromObject
|
|
|
|
|
* @param {Object} object to create an instance from
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Text} an instance
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
2011-04-23 21:39:56 +00:00
|
|
|
fabric.Text.fromObject = function(object) {
|
|
|
|
|
return new fabric.Text(object.text, clone(object));
|
|
|
|
|
};
|
2012-01-18 11:00:59 +00:00
|
|
|
|
2011-04-23 21:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
|
2010-06-09 22:34:55 +00:00
|
|
|
* @static
|
2010-07-09 23:43:50 +00:00
|
|
|
* @method fabric.Text.fromElement
|
2012-08-11 16:59:53 +00:00
|
|
|
* @param element
|
|
|
|
|
* @param options
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Text} an instance
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
2012-08-11 16:59:53 +00:00
|
|
|
fabric.Text.fromElement = function(element, options) {
|
|
|
|
|
if (!element) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
|
|
|
|
|
var options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
|
|
|
|
|
var text = new fabric.Text(element.textContent, options);
|
|
|
|
|
|
|
|
|
|
return text;
|
2011-04-23 21:39:56 +00:00
|
|
|
};
|
2012-06-26 14:42:45 +00:00
|
|
|
|
2012-04-08 13:15:31 +00:00
|
|
|
})(typeof exports != 'undefined' ? exports : this);
|