mirror of
https://github.com/konvajs/konva.git
synced 2025-04-05 20:48:28 +08:00
468 lines
15 KiB
JavaScript
468 lines
15 KiB
JavaScript
(function() {
|
|
// constants
|
|
var AUTO = 'auto',
|
|
CALIBRI = 'Calibri',
|
|
CANVAS = 'canvas',
|
|
CENTER = 'center',
|
|
CHANGE_KINETIC = 'Change.kinetic',
|
|
CONTEXT_2D = '2d',
|
|
DASH = '-',
|
|
EMPTY_STRING = '',
|
|
LEFT = 'left',
|
|
NEW_LINE = '\n',
|
|
TEXT = 'text',
|
|
TEXT_UPPER = 'Text',
|
|
TOP = 'top',
|
|
MIDDLE = 'middle',
|
|
NORMAL = 'normal',
|
|
PX_SPACE = 'px ',
|
|
SPACE = ' ',
|
|
RIGHT = 'right',
|
|
WORD = 'word',
|
|
CHAR = 'char',
|
|
NONE = 'none',
|
|
ATTR_CHANGE_LIST = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'align', 'lineHeight', 'text', 'width', 'height', 'wrap'],
|
|
|
|
// cached variables
|
|
attrChangeListLen = ATTR_CHANGE_LIST.length,
|
|
dummyContext = document.createElement(CANVAS).getContext(CONTEXT_2D);
|
|
|
|
/**
|
|
* Text constructor
|
|
* @constructor
|
|
* @memberof Kinetic
|
|
* @augments Kinetic.Shape
|
|
* @param {Object} config
|
|
* @param {String} [config.fontFamily] default is Calibri
|
|
* @param {Number} [config.fontSize] in pixels. Default is 12
|
|
* @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal
|
|
* @param {String} config.text
|
|
* @param {String} [config.align] can be left, center, or right
|
|
* @param {Number} [config.padding]
|
|
* @param {Number} [config.width] default is auto
|
|
* @param {Number} [config.height] default is auto
|
|
* @param {Number} [config.lineHeight] default is 1
|
|
* @param {String} [config.wrap] can be word, char, or none. Default is word
|
|
* @@shapeParams
|
|
* @@nodeParams
|
|
* @example
|
|
* var text = new Kinetic.Text({<br>
|
|
* x: stage.getWidth() / 2,<br>
|
|
* y: 15,<br>
|
|
* text: 'Simple Text',<br>
|
|
* fontSize: 30,<br>
|
|
* fontFamily: 'Calibri',<br>
|
|
* fill: 'green'<br>
|
|
* });
|
|
*/
|
|
Kinetic.Text = function(config) {
|
|
this.___init(config);
|
|
};
|
|
function _fillFunc(context) {
|
|
context.fillText(this.partialText, 0, 0);
|
|
}
|
|
function _strokeFunc(context) {
|
|
context.strokeText(this.partialText, 0, 0);
|
|
}
|
|
|
|
Kinetic.Text.prototype = {
|
|
___init: function(config) {
|
|
var that = this;
|
|
|
|
if (config.width === undefined) {
|
|
config.width = AUTO;
|
|
}
|
|
if (config.height === undefined) {
|
|
config.height = AUTO;
|
|
}
|
|
|
|
// call super constructor
|
|
Kinetic.Shape.call(this, config);
|
|
|
|
this._fillFunc = _fillFunc;
|
|
this._strokeFunc = _strokeFunc;
|
|
this.className = TEXT_UPPER;
|
|
|
|
// update text data for certain attr changes
|
|
for(var n = 0; n < attrChangeListLen; n++) {
|
|
this.on(ATTR_CHANGE_LIST[n] + CHANGE_KINETIC, that._setTextData);
|
|
}
|
|
|
|
this._setTextData();
|
|
},
|
|
drawFunc: function(context) {
|
|
var _context = context._context,
|
|
p = this.getPadding(),
|
|
fontStyle = this.getFontStyle(),
|
|
fontSize = this.getFontSize(),
|
|
fontFamily = this.getFontFamily(),
|
|
textHeight = this.getTextHeight(),
|
|
lineHeightPx = this.getLineHeight() * textHeight,
|
|
textArr = this.textArr,
|
|
textArrLen = textArr.length,
|
|
totalWidth = this.getWidth();
|
|
|
|
_context.font = this._getContextFont();
|
|
_context.textBaseline = MIDDLE;
|
|
_context.textAlign = LEFT;
|
|
_context.save();
|
|
_context.translate(p, 0);
|
|
_context.translate(0, p + textHeight / 2);
|
|
|
|
// draw text lines
|
|
for(var n = 0; n < textArrLen; n++) {
|
|
var obj = textArr[n],
|
|
text = obj.text,
|
|
width = obj.width;
|
|
|
|
// horizontal alignment
|
|
_context.save();
|
|
if(this.getAlign() === RIGHT) {
|
|
_context.translate(totalWidth - width - p * 2, 0);
|
|
}
|
|
else if(this.getAlign() === CENTER) {
|
|
_context.translate((totalWidth - width - p * 2) / 2, 0);
|
|
}
|
|
|
|
this.partialText = text;
|
|
context.fillStroke(this);
|
|
_context.restore();
|
|
_context.translate(0, lineHeightPx);
|
|
}
|
|
_context.restore();
|
|
},
|
|
drawHitFunc: function(context) {
|
|
var _context = context._context,
|
|
width = this.getWidth(),
|
|
height = this.getHeight();
|
|
|
|
_context.beginPath();
|
|
_context.rect(0, 0, width, height);
|
|
_context.closePath();
|
|
context.fillStroke(this);
|
|
},
|
|
/**
|
|
* set text
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {String} text
|
|
*/
|
|
setText: function(text) {
|
|
var str = Kinetic.Util._isString(text) ? text : text.toString();
|
|
this._setAttr(TEXT, str);
|
|
},
|
|
/**
|
|
* get width
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
getWidth: function() {
|
|
return this.attrs.width === AUTO ? this.getTextWidth() + this.getPadding() * 2 : this.attrs.width;
|
|
},
|
|
/**
|
|
* get height
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
getHeight: function() {
|
|
return this.attrs.height === AUTO ? (this.getTextHeight() * this.textArr.length * this.getLineHeight()) + this.getPadding() * 2 : this.attrs.height;
|
|
},
|
|
/**
|
|
* get text width
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
getTextWidth: function() {
|
|
return this.textWidth;
|
|
},
|
|
/**
|
|
* get text height
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
getTextHeight: function() {
|
|
return this.textHeight;
|
|
},
|
|
_getTextSize: function(text) {
|
|
var _context = dummyContext,
|
|
fontSize = this.getFontSize(),
|
|
metrics;
|
|
|
|
_context.save();
|
|
_context.font = this._getContextFont();
|
|
|
|
metrics = _context.measureText(text);
|
|
_context.restore();
|
|
return {
|
|
width: metrics.width,
|
|
height: parseInt(fontSize, 10)
|
|
};
|
|
},
|
|
_getContextFont: function() {
|
|
return this.getFontStyle() + SPACE + this.getFontSize() + PX_SPACE + this.getFontFamily();
|
|
},
|
|
_addTextLine: function (line, width, height) {
|
|
return this.textArr.push({text: line, width: width});
|
|
},
|
|
_getTextWidth: function (text) {
|
|
return dummyContext.measureText(text).width;
|
|
},
|
|
_setTextData: function () {
|
|
var lines = this.getText().split('\n'),
|
|
fontSize = +this.getFontSize(),
|
|
textWidth = 0,
|
|
lineHeightPx = this.getLineHeight() * fontSize,
|
|
width = this.attrs.width,
|
|
height = this.attrs.height,
|
|
fixedWidth = width !== AUTO,
|
|
fixedHeight = height !== AUTO,
|
|
padding = this.getPadding(),
|
|
maxWidth = width - padding * 2,
|
|
maxHeightPx = height - padding * 2,
|
|
currentHeightPx = 0,
|
|
wrap = this.getWrap(),
|
|
shouldWrap = wrap !== NONE,
|
|
wrapAtWord = wrap !== CHAR && shouldWrap;
|
|
|
|
this.textArr = [];
|
|
dummyContext.save();
|
|
dummyContext.font = this.getFontStyle() + SPACE + fontSize + PX_SPACE + this.getFontFamily();
|
|
for (var i = 0, max = lines.length; i < max; ++i) {
|
|
var line = lines[i],
|
|
lineWidth = this._getTextWidth(line);
|
|
if (fixedWidth && lineWidth > maxWidth) {
|
|
/*
|
|
* if width is fixed and line does not fit entirely
|
|
* break the line into multiple fitting lines
|
|
*/
|
|
while (line.length > 0) {
|
|
/*
|
|
* use binary search to find the longest substring that
|
|
* that would fit in the specified width
|
|
*/
|
|
var low = 0, high = line.length,
|
|
match = '', matchWidth = 0;
|
|
while (low < high) {
|
|
var mid = (low + high) >>> 1,
|
|
substr = line.slice(0, mid + 1),
|
|
substrWidth = this._getTextWidth(substr);
|
|
if (substrWidth <= maxWidth) {
|
|
low = mid + 1;
|
|
match = substr;
|
|
matchWidth = substrWidth;
|
|
} else {
|
|
high = mid;
|
|
}
|
|
}
|
|
/*
|
|
* 'low' is now the index of the substring end
|
|
* 'match' is the substring
|
|
* 'matchWidth' is the substring width in px
|
|
*/
|
|
if (match) {
|
|
// a fitting substring was found
|
|
if (wrapAtWord) {
|
|
// try to find a space or dash where wrapping could be done
|
|
var wrapIndex = Math.max(match.lastIndexOf(SPACE),
|
|
match.lastIndexOf(DASH)) + 1;
|
|
if (wrapIndex > 0) {
|
|
// re-cut the substring found at the space/dash position
|
|
low = wrapIndex;
|
|
match = match.slice(0, low);
|
|
matchWidth = this._getTextWidth(match);
|
|
}
|
|
}
|
|
this._addTextLine(match, matchWidth);
|
|
currentHeightPx += lineHeightPx;
|
|
if (!shouldWrap ||
|
|
(fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx)) {
|
|
/*
|
|
* stop wrapping if wrapping is disabled or if adding
|
|
* one more line would overflow the fixed height
|
|
*/
|
|
break;
|
|
}
|
|
line = line.slice(low);
|
|
if (line.length > 0) {
|
|
// Check if the remaining text would fit on one line
|
|
lineWidth = this._getTextWidth(line);
|
|
if (lineWidth <= maxWidth) {
|
|
// if it does, add the line and break out of the loop
|
|
this._addTextLine(line, lineWidth);
|
|
currentHeightPx += lineHeightPx;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// not even one character could fit in the element, abort
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// element width is automatically adjusted to max line width
|
|
this._addTextLine(line, lineWidth);
|
|
currentHeightPx += lineHeightPx;
|
|
textWidth = Math.max(textWidth, lineWidth);
|
|
}
|
|
// if element height is fixed, abort if adding one more line would overflow
|
|
if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) {
|
|
break;
|
|
}
|
|
}
|
|
dummyContext.restore();
|
|
this.textHeight = fontSize;
|
|
this.textWidth = textWidth;
|
|
}
|
|
};
|
|
Kinetic.Util.extend(Kinetic.Text, Kinetic.Shape);
|
|
|
|
// add getters setters
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontFamily', CALIBRI);
|
|
|
|
/**
|
|
* set font family
|
|
* @name setFontFamily
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {String} fontFamily
|
|
*/
|
|
|
|
/**
|
|
* get font family
|
|
* @name getFontFamily
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontSize', 12);
|
|
|
|
/**
|
|
* set font size in pixels
|
|
* @name setFontSize
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {int} fontSize
|
|
*/
|
|
|
|
/**
|
|
* get font size
|
|
* @name getFontSize
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontStyle', NORMAL);
|
|
|
|
/**
|
|
* set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default.
|
|
* @name setFontStyle
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {String} fontStyle
|
|
*/
|
|
|
|
/**
|
|
* get font style
|
|
* @name getFontStyle
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'padding', 0);
|
|
|
|
/**
|
|
* set padding
|
|
* @name setPadding
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {int} padding
|
|
*/
|
|
|
|
/**
|
|
* get padding
|
|
* @name getPadding
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'align', LEFT);
|
|
|
|
/**
|
|
* set horizontal align of text
|
|
* @name setAlign
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {String} align align can be 'left', 'center', or 'right'
|
|
*/
|
|
|
|
/**
|
|
* get horizontal align
|
|
* @name getAlign
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'lineHeight', 1);
|
|
|
|
/**
|
|
* set line height
|
|
* @name setLineHeight
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {Number} lineHeight default is 1
|
|
*/
|
|
|
|
/**
|
|
* get line height
|
|
* @name getLineHeight
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Text, 'wrap', WORD);
|
|
|
|
/**
|
|
* set wrap
|
|
* @name setWrap
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {String} wrap can be word, char, or none. Default is word
|
|
*/
|
|
|
|
/**
|
|
* get wrap
|
|
* @name getWrap
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addGetter(Kinetic.Text, TEXT, EMPTY_STRING);
|
|
|
|
/**
|
|
* get text
|
|
* @name getText
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
*/
|
|
|
|
Kinetic.Factory.addSetter(Kinetic.Text, 'width');
|
|
|
|
/**
|
|
* set width
|
|
* @name setWidth
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {Number|String} width default is auto
|
|
*/
|
|
|
|
Kinetic.Factory.addSetter(Kinetic.Text, 'height');
|
|
|
|
/**
|
|
* set height
|
|
* @name setHeight
|
|
* @method
|
|
* @memberof Kinetic.Text.prototype
|
|
* @param {Number|String} height default is auto
|
|
*/
|
|
})();
|