first pass at implementing auto word wrap. Still have a few edge cases to cover. Removed vertical align property as it is no longer relevant

This commit is contained in:
Eric Rowell 2012-06-30 00:40:54 -07:00
parent 07ef653441
commit 6663b9e612
5 changed files with 159 additions and 146 deletions

130
dist/kinetic-core.js vendored
View File

@ -3,7 +3,7 @@
* http://www.kineticjs.com/
* Copyright 2012, Eric Rowell
* Licensed under the MIT or GPL Version 2 licenses.
* Date: Jun 27 2012
* Date: Jun 30 2012
*
* Copyright (C) 2011 - 2012 by Eric Rowell
*
@ -569,11 +569,8 @@ Kinetic.Node.prototype = {
/**
* set attrs
* @param {Object} config
* @param {Boolean} skipPublish set the second argument to true
* to skip publication of the attr change event in case you want
* to silently update an attribute
*/
setAttrs: function(config, skipPublish) {
setAttrs: function(config) {
var go = Kinetic.GlobalObject;
var that = this;
@ -652,7 +649,7 @@ Kinetic.Node.prototype = {
* only fire change event for root
* level attrs
*/
if(level === 0 && !skipPublish) {
if(level === 0) {
that._fireChangeEvent(key);
}
}
@ -3891,9 +3888,11 @@ Kinetic.Text = function(config) {
width: 'auto',
height: 'auto',
detectionType: 'path',
cornerRadius: 0
cornerRadius: 0,
lineHeight: 1.2
});
this.dummyCanvas = document.createElement('canvas');
this.shapeType = "Text";
this.boxShape = new Kinetic.Rect({});
@ -3913,40 +3912,24 @@ Kinetic.Text = function(config) {
var p = that.attrs.padding;
context.beginPath();
context.rect(p / 2, p / 2, that.getBoxWidth() - p, that.getBoxHeight() - p);
context.closePath();
if(that.attrs.width !== 'auto' && (that.getTextWidth() > that.getBoxWidth() - p || that.getTextHeight() > that.getBoxHeight() - p)) {
context.clip();
}
var lineHeightPx = that.attrs.lineHeight * that.getTextHeight();
// horizontal align
if(that.attrs.align === 'left') {
context.translate(p / 2, 0);
}
else if(that.attrs.align === 'center') {
context.translate((that.getBoxWidth() - that.getTextWidth()) / 2, 0);
}
// right
else {
context.translate(that.getBoxWidth() - that.getTextWidth() - p / 2, 0);
}
context.translate(p, 0);
// vertical align
if(that.attrs.verticalAlign === 'top') {
context.translate(0, (p + that.getTextHeight()) / 2);
}
else if(that.attrs.verticalAlign === 'middle') {
context.translate(0, that.getBoxHeight() / 2);
}
// bottom
else {
context.translate(0, that.getBoxHeight() - (p + that.getTextHeight()) / 2);
context.translate(0, p + that.getTextHeight() / 2);
// draw text lines
var textArr = that.textArr;
for(var n = 0; n < textArr.length; n++) {
var text = textArr[n];
this.fillText(text);
this.strokeText(text);
context.translate(0, lineHeightPx);
}
this.fillText(that.attrs.text);
this.strokeText(that.attrs.text);
context.restore();
}
});
@ -3958,14 +3941,19 @@ Kinetic.Text = function(config) {
this.add(this.boxShape);
this.add(this.textShape);
// bind events to sync attrs
// sync attrs
var attrs = ['width', 'height', 'cornerRadius', 'stroke', 'strokeWidth', 'fill', 'shadow', 'detectionType', 'textFill', 'textStroke', 'textStrokeWidth'];
var that = this;
for(var n = 0; n < attrs.length; n++) {
var attr = attrs[n];
this.on(attr + 'Change', this._syncAttrs);
this.on(attr + 'Change', function(evt) {
if(!evt.shape) {
that._syncAttrs();
}
});
}
that._setTextData();
this._syncAttrs();
};
/*
@ -3976,41 +3964,61 @@ Kinetic.Text.prototype = {
* get text width in pixels
*/
getTextWidth: function() {
return this.getTextSize().width;
return this.textWidth;
},
/**
* get text height in pixels
*/
getTextHeight: function() {
return this.getTextSize().height;
return this.textHeight;
},
/**
* get text size in pixels
* get box width
*/
getTextSize: function() {
var dummyCanvas = document.createElement('canvas');
getBoxWidth: function() {
return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width;
},
/**
* get box height
*/
getBoxHeight: function() {
return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height;
},
_getTextSize: function(text) {
var dummyCanvas = this.dummyCanvas;
var context = dummyCanvas.getContext('2d');
context.save();
context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily;
var metrics = context.measureText(this.attrs.text);
var metrics = context.measureText(text);
context.restore();
return {
width: metrics.width,
height: parseInt(this.attrs.fontSize, 10)
};
},
/**
* get box width
*/
getBoxWidth: function() {
return (this.attrs.width === 'auto' ? this.getTextWidth() : this.attrs.width) + this.attrs.padding;
},
/**
* get box height
*/
getBoxHeight: function() {
return (this.attrs.height === 'auto' ? this.getTextHeight() : this.attrs.height) + this.attrs.padding;
_setTextData: function() {
var charArr = this.attrs.text.split('');
var arr = [];
var lastWord = '';
this.textArr = [];
this.textWidth = 0;
this.textHeight = 0;
while(charArr.length > 0) {
var line = lastWord;
while(charArr[0] !== undefined && (this.attrs.width === 'auto' || this._getTextSize(line + charArr[0]).width < this.attrs.width - this.attrs.padding)) {
lastWord = charArr[0] === ' ' || charArr[0] === '-' ? '' : lastWord + charArr[0];
line += charArr.splice(0, 1);
}
if(charArr.length > 0 && charArr[0] !== ' ' && charArr[0] !== '-') {
line = line.substring(0, line.lastIndexOf(lastWord));
}
this.textWidth = Math.max(this.textWidth, this._getTextSize(line).width);
arr.push(line);
}
this.textHeight = this._getTextSize(arr[0]).height;
this.textArr = arr;
},
_syncAttrs: function() {
this.boxShape.setAttrs({
@ -4040,7 +4048,7 @@ Kinetic.Text.prototype = {
Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Group);
// add setters and getters
Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'verticalAlign', 'text', 'width', 'height', 'cornerRadius', 'fill', 'stroke', 'strokeWidth', 'shadow']);
Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight', 'text', 'width', 'height', 'cornerRadius', 'fill', 'stroke', 'strokeWidth', 'shadow']);
/**
* set font family
@ -4099,10 +4107,10 @@ Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize',
*/
/**
* set vertical align of text
* @name setVerticalAlign
* set line height
* @name setLineHeight
* @methodOf Kinetic.Text.prototype
* @param {String} verticalAlign verticalAlign can be "top", "middle", or "bottom"
* @param {Number} lineHeight default is 1.2
*/
/**
@ -4182,8 +4190,8 @@ Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize',
*/
/**
* get vertical align
* @name getVerticalAlign
* get line height
* @name getLineHeight
* @methodOf Kinetic.Text.prototype
*/

File diff suppressed because one or more lines are too long

View File

@ -172,11 +172,8 @@ Kinetic.Node.prototype = {
/**
* set attrs
* @param {Object} config
* @param {Boolean} skipPublish set the second argument to true
* to skip publication of the attr change event in case you want
* to silently update an attribute
*/
setAttrs: function(config, skipPublish) {
setAttrs: function(config) {
var go = Kinetic.GlobalObject;
var that = this;
@ -255,7 +252,7 @@ Kinetic.Node.prototype = {
* only fire change event for root
* level attrs
*/
if(level === 0 && !skipPublish) {
if(level === 0) {
that._fireChangeEvent(key);
}
}

View File

@ -19,9 +19,11 @@ Kinetic.Text = function(config) {
width: 'auto',
height: 'auto',
detectionType: 'path',
cornerRadius: 0
cornerRadius: 0,
lineHeight: 1.2
});
this.dummyCanvas = document.createElement('canvas');
this.shapeType = "Text";
this.boxShape = new Kinetic.Rect({});
@ -41,40 +43,24 @@ Kinetic.Text = function(config) {
var p = that.attrs.padding;
context.beginPath();
context.rect(p / 2, p / 2, that.getBoxWidth() - p, that.getBoxHeight() - p);
context.closePath();
if(that.attrs.width !== 'auto' && (that.getTextWidth() > that.getBoxWidth() - p || that.getTextHeight() > that.getBoxHeight() - p)) {
context.clip();
}
var lineHeightPx = that.attrs.lineHeight * that.getTextHeight();
// horizontal align
if(that.attrs.align === 'left') {
context.translate(p / 2, 0);
}
else if(that.attrs.align === 'center') {
context.translate((that.getBoxWidth() - that.getTextWidth()) / 2, 0);
}
// right
else {
context.translate(that.getBoxWidth() - that.getTextWidth() - p / 2, 0);
}
context.translate(p, 0);
// vertical align
if(that.attrs.verticalAlign === 'top') {
context.translate(0, (p + that.getTextHeight()) / 2);
}
else if(that.attrs.verticalAlign === 'middle') {
context.translate(0, that.getBoxHeight() / 2);
}
// bottom
else {
context.translate(0, that.getBoxHeight() - (p + that.getTextHeight()) / 2);
context.translate(0, p + that.getTextHeight() / 2);
// draw text lines
var textArr = that.textArr;
for(var n = 0; n < textArr.length; n++) {
var text = textArr[n];
this.fillText(text);
this.strokeText(text);
context.translate(0, lineHeightPx);
}
this.fillText(that.attrs.text);
this.strokeText(that.attrs.text);
context.restore();
}
});
@ -86,14 +72,19 @@ Kinetic.Text = function(config) {
this.add(this.boxShape);
this.add(this.textShape);
// bind events to sync attrs
// sync attrs
var attrs = ['width', 'height', 'cornerRadius', 'stroke', 'strokeWidth', 'fill', 'shadow', 'detectionType', 'textFill', 'textStroke', 'textStrokeWidth'];
var that = this;
for(var n = 0; n < attrs.length; n++) {
var attr = attrs[n];
this.on(attr + 'Change', this._syncAttrs);
this.on(attr + 'Change', function(evt) {
if(!evt.shape) {
that._syncAttrs();
}
});
}
that._setTextData();
this._syncAttrs();
};
/*
@ -104,41 +95,61 @@ Kinetic.Text.prototype = {
* get text width in pixels
*/
getTextWidth: function() {
return this.getTextSize().width;
return this.textWidth;
},
/**
* get text height in pixels
*/
getTextHeight: function() {
return this.getTextSize().height;
return this.textHeight;
},
/**
* get text size in pixels
* get box width
*/
getTextSize: function() {
var dummyCanvas = document.createElement('canvas');
getBoxWidth: function() {
return this.attrs.width === 'auto' ? this.getTextWidth() + this.attrs.padding * 2 : this.attrs.width;
},
/**
* get box height
*/
getBoxHeight: function() {
return this.attrs.height === 'auto' ? (this.getTextHeight() * this.textArr.length * this.attrs.lineHeight) + this.attrs.padding * 2 : this.attrs.height;
},
_getTextSize: function(text) {
var dummyCanvas = this.dummyCanvas;
var context = dummyCanvas.getContext('2d');
context.save();
context.font = this.attrs.fontStyle + ' ' + this.attrs.fontSize + 'pt ' + this.attrs.fontFamily;
var metrics = context.measureText(this.attrs.text);
var metrics = context.measureText(text);
context.restore();
return {
width: metrics.width,
height: parseInt(this.attrs.fontSize, 10)
};
},
/**
* get box width
*/
getBoxWidth: function() {
return (this.attrs.width === 'auto' ? this.getTextWidth() : this.attrs.width) + this.attrs.padding;
},
/**
* get box height
*/
getBoxHeight: function() {
return (this.attrs.height === 'auto' ? this.getTextHeight() : this.attrs.height) + this.attrs.padding;
_setTextData: function() {
var charArr = this.attrs.text.split('');
var arr = [];
var lastWord = '';
this.textArr = [];
this.textWidth = 0;
this.textHeight = 0;
while(charArr.length > 0) {
var line = lastWord;
while(charArr[0] !== undefined && (this.attrs.width === 'auto' || this._getTextSize(line + charArr[0]).width < this.attrs.width - this.attrs.padding)) {
lastWord = charArr[0] === ' ' || charArr[0] === '-' ? '' : lastWord + charArr[0];
line += charArr.splice(0, 1);
}
if(charArr.length > 0 && charArr[0] !== ' ' && charArr[0] !== '-') {
line = line.substring(0, line.lastIndexOf(lastWord));
}
this.textWidth = Math.max(this.textWidth, this._getTextSize(line).width);
arr.push(line);
}
this.textHeight = this._getTextSize(arr[0]).height;
this.textArr = arr;
},
_syncAttrs: function() {
this.boxShape.setAttrs({
@ -168,7 +179,7 @@ Kinetic.Text.prototype = {
Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Group);
// add setters and getters
Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'verticalAlign', 'text', 'width', 'height', 'cornerRadius', 'fill', 'stroke', 'strokeWidth', 'shadow']);
Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth', 'padding', 'align', 'lineHeight', 'text', 'width', 'height', 'cornerRadius', 'fill', 'stroke', 'strokeWidth', 'shadow']);
/**
* set font family
@ -227,10 +238,10 @@ Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize',
*/
/**
* set vertical align of text
* @name setVerticalAlign
* set line height
* @name setLineHeight
* @methodOf Kinetic.Text.prototype
* @param {String} verticalAlign verticalAlign can be "top", "middle", or "bottom"
* @param {Number} lineHeight default is 1.2
*/
/**
@ -310,8 +321,8 @@ Kinetic.GlobalObject.addSettersGetters(Kinetic.Text, ['fontFamily', 'fontSize',
*/
/**
* get vertical align
* @name getVerticalAlign
* get line height
* @name getLineHeight
* @methodOf Kinetic.Text.prototype
*/

View File

@ -2848,7 +2848,6 @@ Test.prototype.tests = {
textFill: '#888',
textStroke: '#333',
align: 'right',
verticalAlign: 'bottom',
width: 400,
height: 100,
padding: 40,
@ -2885,7 +2884,6 @@ Test.prototype.tests = {
test(text.getTextFill() == '#888', 'text fill should be #888');
test(text.getTextStroke() == '#333', 'text fill should be #333');
test(text.getAlign() === 'right', 'text should be aligned right');
test(text.getVerticalAlign() === 'bottom', 'text should be vertically aligned at the bottom');
test(text.getWidth() === 400, 'width should be 400');
test(text.getHeight() === 100, 'height should be 100');
test(text.getPadding() === 40, 'padding should be 40');
@ -2906,7 +2904,6 @@ Test.prototype.tests = {
text.setTextFill('green');
text.setTextStroke('yellow');
text.setAlign('left');
text.setVerticalAlign('top');
text.setWidth(300);
text.setHeight(75);
text.setPadding(20);
@ -2929,7 +2926,6 @@ Test.prototype.tests = {
test(text.getTextFill() == 'green', 'text fill should be green');
test(text.getTextStroke() == 'yellow', 'text fill should be yellow');
test(text.getAlign() === 'left', 'text should be aligned left');
test(text.getVerticalAlign() === 'top', 'text should be vertically aligned at the top');
test(text.getWidth() === 300, 'width should be 300');
test(text.getHeight() === 75, 'height should be 75');
test(text.getPadding() === 20, 'padding should be 20');
@ -2939,7 +2935,7 @@ Test.prototype.tests = {
test(text.getDetectionType() === 'pixel', 'text detection type should be pixel');
},
'SHAPE - get text metrics': function(containerId) {
'SHAPE - text multi line': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
@ -2950,32 +2946,33 @@ Test.prototype.tests = {
var text = new Kinetic.Text({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
text: 'Hello World!',
fontSize: 60,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
text: 'All the world \'s a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.',
fontSize: 16,
fontFamily: 'Calibri',
fontStyle: 'normal',
textStroke: 'green',
fontStyle: 'italic',
align: 'center',
verticalAlign: 'middle',
textFill: '#555',
width: 385,
padding: 20,
shadow: {
color: 'black',
blur: 2,
offset: [5, 5],
alpha: 1
}
blur: 1,
offset: [10, 10],
alpha: 0.2
},
cornerRadius: 10,
draggable: true,
detectionType: 'path'
});
// test text width before adding it to stage
test(text.getTextWidth() > 0, 'text width should have a value');
// center text box
text.setOffset(text.getBoxWidth() / 2, text.getBoxHeight() / 2);
layer.add(text);
stage.add(layer);
test(text.getTextSize().width > 0, 'text width should have a value');
test(text.getTextSize().height > 0, 'text height should have a value');
test(text.getTextWidth() > 0, 'text width should have a value');
test(text.getTextHeight() > 0, 'text height should have a value');
},
'SHAPE - get shape name': function(containerId) {
var stage = new Kinetic.Stage({