Use this.styles as if it's not affected by line-wraps.

This commit is contained in:
inssein 2015-06-10 17:12:54 -07:00
parent 4843a61a84
commit 9eb597c729
4 changed files with 338 additions and 50 deletions

View file

@ -628,8 +628,8 @@
lineIndex = cursorLocation.lineIndex,
charIndex = cursorLocation.charIndex;
if (!this.styles[lineIndex]) {
this.styles[lineIndex] = { };
if (!this._getLineStyle(lineIndex)) {
this._setLineStyle(lineIndex, {});
}
if (_chars === '\n') {

View file

@ -5,6 +5,7 @@
* 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) {
@ -38,4 +39,145 @@
}
};
var clone = fabric.util.object.clone;
fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ {
/**
* @private
*/
_removeExtraneousStyles: function() {
//for (var prop in this._styleMap) {
// if (!this._textLines[prop]) {
// delete this.styles[this._styleMap[prop].line];
// }
//}
},
/**
* Inserts style object for a given line/char index
* @param {Number} lineIndex Index of a line
* @param {Number} charIndex Index of a char
* @param {Object} [style] Style object to insert, if given
*/
insertCharStyleObject: function(lineIndex, charIndex, style) {
// adjust lineIndex and charIndex
var map = this._styleMap[lineIndex];
lineIndex = map.line;
charIndex = map.offset + charIndex;
this.callSuper('insertCharStyleObject', lineIndex, charIndex, style);
},
/**
* Inserts new style object
* @param {Number} lineIndex Index of a line
* @param {Number} charIndex Index of a char
* @param {Boolean} isEndOfLine True if it's end of line
*/
insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
// adjust lineIndex and charIndex
var map = this._styleMap[lineIndex];
lineIndex = map.line;
charIndex = map.offset + charIndex;
this.callSuper('insertNewlineStyleObject', lineIndex, charIndex, isEndOfLine);
},
/**
* Shifts line styles up or down. This function is slightly different than the one in
* itext_behaviour as it takes into account the styleMap.
*
* @param {Number} lineIndex Index of a line
* @param {Number} offset Can be -1 or +1
*/
shiftLineStyles: function(lineIndex, offset) {
// shift all line styles by 1 upward
var clonedStyles = clone(this.styles);
// adjust line index
var map = this._styleMap[lineIndex];
lineIndex = map.line;
for (var line in this.styles) {
var numericLine = parseInt(line, 10);
if (numericLine > lineIndex) {
this.styles[numericLine + offset] = clonedStyles[numericLine];
if (!clonedStyles[numericLine - offset]) {
delete this.styles[numericLine];
}
}
}
//TODO: evaluate if delete old style lines with offset -1
},
/**
* Figure out programatically the text on previous actual line (actual = separated by \n);
*
* @param {Number} lineIndex
* @returns {String}
* @private
*/
_getTextOnPreviousLine: function(lineIndex) {
var textOnPreviousLine = this._textLines[lineIndex - 1];
while(this._styleMap[lineIndex - 2] && this._styleMap[lineIndex - 2].line === this._styleMap[lineIndex - 1].line) {
textOnPreviousLine = this._textLines[lineIndex - 2] + textOnPreviousLine;
lineIndex--;
}
return textOnPreviousLine;
},
/**
* Removes style object
* @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
* @param {Number} [index] Optional index. When not given, current selectionStart is used.
*/
removeStyleObject: function(isBeginningOfLine, index) {
var cursorLocation = this.get2DCursorLocation(index),
map = this._styleMap[cursorLocation.lineIndex],
lineIndex = map.line,
charIndex = map.offset + cursorLocation.charIndex;
if (isBeginningOfLine) {
var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex),
newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0;
if (!this.styles[lineIndex - 1]) {
this.styles[lineIndex - 1] = { };
}
for (charIndex in this.styles[lineIndex]) {
this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]
= this.styles[lineIndex][charIndex];
}
this.shiftLineStyles(cursorLocation.lineIndex, -1);
}
else {
var currentLineStyles = this.styles[lineIndex];
if (currentLineStyles) {
delete currentLineStyles[charIndex];
//console.log('deleting', lineIndex, charIndex + offset);
}
var currentLineStylesCloned = clone(currentLineStyles);
// shift all styles by 1 backwards
for (var i in currentLineStylesCloned) {
var numericIndex = parseInt(i, 10);
if (numericIndex >= charIndex && numericIndex !== 0) {
currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];
delete currentLineStyles[numericIndex];
}
}
}
}
});
})();

View file

@ -260,11 +260,8 @@
}
var loc = this.get2DCursorLocation(startIndex);
if (this.styles[loc.lineIndex]) {
return this.styles[loc.lineIndex][loc.charIndex] || { };
}
return { };
var style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex);
return style || {};
},
/**
@ -293,13 +290,15 @@
_extendStyles: function(index, styles) {
var loc = this.get2DCursorLocation(index);
if (!this.styles[loc.lineIndex]) {
this.styles[loc.lineIndex] = { };
if (!this._getLineStyle(loc.lineIndex)) {
this._setLineStyle(loc.lineIndex, {})
}
if (!this.styles[loc.lineIndex][loc.charIndex]) {
this.styles[loc.lineIndex][loc.charIndex] = { };
if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) {
this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {});
}
fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles);
fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles);
},
/**
@ -378,7 +377,7 @@
* @return {Object} Character style
*/
getCurrentCharStyle: function(lineIndex, charIndex) {
var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)];
var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
return {
fontSize: style && style.fontSize || this.fontSize,
@ -400,10 +399,8 @@
* @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;
var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
return style && style.fontSize ? style.fontSize : this.fontSize;
},
/**
@ -413,10 +410,8 @@
* @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;
var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
return style && style.fill ? style.fill : this.cursorColor;
},
/**
@ -644,11 +639,11 @@
* @param {Number} lineHeight Height of the line
*/
_renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
var decl, charWidth, charHeight,
var charWidth, charHeight,
decl = this._getStyleDeclaration(lineIndex, i),
offset = this._fontSizeFraction * lineHeight / this.lineHeight;
if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
if (decl) {
var shouldStroke = decl.stroke || this.stroke,
shouldFill = decl.fill || this.fill;
@ -803,13 +798,14 @@
heightOfLine / this.lineHeight
);
}
if (this.styles[i]) {
if (this._getLineStyle(i)) {
for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) {
if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) {
var style = this._getStyleDeclaration(i, j);
if (style && style.textBackgroundColor) {
var _char = this._textLines[i][j];
ctx.fillStyle = this.styles[i][j].textBackgroundColor;
ctx.fillStyle = style.textBackgroundColor;
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),
@ -846,9 +842,7 @@
* @param {Object} [decl]
*/
_applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
var styleDeclaration = decl ||
(this.styles[lineIndex] &&
this.styles[lineIndex][charIndex]);
var styleDeclaration = decl || this._getStyleDeclaration(lineIndex, charIndex);
if (styleDeclaration) {
// cloning so that original style object is not polluted with following font declarations
@ -917,14 +911,64 @@
},
/**
* @private
* @param {Number} lineIndex
* @param {Number} charIndex
* @param {Boolean} [returnCloneOrEmpty=false]
* @private
*/
_getStyleDeclaration: function(lineIndex, charIndex) {
return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
? clone(this.styles[lineIndex][charIndex])
: { };
_getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
if(returnCloneOrEmpty) {
return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
? clone(this.styles[lineIndex][charIndex])
: { };
}
return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null;
},
/**
* @param {Number} lineIndex
* @param {Number} charIndex
* @param {Object} style
* @private
*/
_setStyleDeclaration: function(lineIndex, charIndex, style) {
this.styles[lineIndex][charIndex] = style;
},
/**
*
* @param {Number} lineIndex
* @param {Number} charIndex
* @private
*/
_deleteStyleDeclaration: function(lineIndex, charIndex) {
delete this.styles[lineIndex][charIndex];
},
/**
* @param {Number} lineIndex
* @private
*/
_getLineStyle: function(lineIndex) {
return this.styles[lineIndex];
},
/**
* @param {Number} lineIndex
* @param {Object} syle
* @private
*/
_setLineStyle: function(lineIndex, syle) {
this.styles[lineIndex] = style;
},
/**
* @param {Number} lineIndex
* @private
*/
_deleteLineStyle: function(lineIndex) {
delete this.styles[lineIndex];
},
/**
@ -936,7 +980,7 @@
return this._getWidthOfSpace(ctx, lineIndex);
}
var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);
var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex, true);
this._applyFontStyles(styleDeclaration);
var cacheProp = this._getCacheProp(_char, styleDeclaration);
@ -956,10 +1000,8 @@
* @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;
var style = this._getStyleDeclaration(lineIndex, charIndex);
return style && style.fontSize ? style.fontSize : this.fontSize;
},
/**

View file

@ -70,9 +70,110 @@
this._setTextStyles(ctx);
}
this._textLines = this._splitTextIntoLines();
this._styleMap = this._generateStyleMap();
this._clearCache();
this.height = this._getTextHeight(ctx);
},
_generateStyleMap: function() {
var realLineCount = 0;
var realLineCharCount = 0;
var charCount = 0;
var map = {};
for(var i = 0; i < this._textLines.length; i++) {
if(this.text[charCount] === '\n') {
realLineCharCount = 0;
charCount++;
realLineCount++;
}
map[i] = {line: realLineCount, offset: realLineCharCount};
charCount += this._textLines[i].length;
realLineCharCount += this._textLines[i].length;
}
return map;
},
/**
* @param {Number} lineIndex
* @param {Number} charIndex
* @param {Boolean} [returnCloneOrEmpty=false]
* @private
*/
_getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
if(this._styleMap) {
var map = this._styleMap[lineIndex];
lineIndex = map.line;
charIndex = map.offset + charIndex;
}
if(returnCloneOrEmpty) {
return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
? clone(this.styles[lineIndex][charIndex])
: { };
}
return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null;
},
/**
* @param {Number} lineIndex
* @param {Number} charIndex
* @param {Object} style
* @private
*/
_setStyleDeclaration: function(lineIndex, charIndex, style) {
var map = this._styleMap[lineIndex];
lineIndex = map.line;
charIndex = map.offset + charIndex;
this.styles[lineIndex][charIndex] = style;
},
/**
* @param {Number} lineIndex
* @param {Number} charIndex
* @private
*/
_deleteStyleDeclaration: function(lineIndex, charIndex) {
var map = this._styleMap[lineIndex];
lineIndex = map.line;
charIndex = map.offset + charIndex;
delete this.styles[lineIndex][charIndex];
},
/**
* @param {Number} lineIndex
* @private
*/
_getLineStyle: function(lineIndex) {
var map = this._styleMap[lineIndex];
return this.styles[map.line];
},
/**
* @param {Number} lineIndex
* @param {Object} style
* @private
*/
_setLineStyle: function(lineIndex, style) {
var map = this._styleMap[lineIndex];
this.styles[map.line] = style;
},
/**
* @param {Number} lineIndex
* @private
*/
_deleteLineStyle: function(lineIndex) {
var map = this._styleMap[lineIndex];
delete this.styles[map.line];
},
/**
* Wraps text using the 'width' property of Textbox. First this function
* splits text on newlines, so we preserve newlines entered by the user.
@ -83,12 +184,10 @@
* @returns {Array} Array of lines
*/
_wrapText: function (ctx, text) {
var lines = text.split(this._reNewline), wrapped = [], lineIndex = 0, newLines, i;
var lines = text.split(this._reNewline), wrapped = [], i;
for (i = 0; i < lines.length; i++) {
newLines = this._wrapLine(ctx, lines[i], lineIndex);
lineIndex += newLines.length;
wrapped = wrapped.concat(newLines);
wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i));
}
return wrapped;
@ -108,8 +207,10 @@
var width = 0, decl;
charOffset = charOffset || 0;
for (var i = charOffset; i < charOffset + text.length; i++) {
if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
for (var i = 0; i < text.length; i++) {
decl = this._getStyleDeclaration(lineIndex, i + charOffset);
if (decl) {
ctx.save();
width += this._applyCharStylesGetWidth(ctx, text[i], lineIndex, i, decl);
ctx.restore();
@ -126,6 +227,7 @@
* 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
* @param {Number} lineIndex
* @returns {Array} Array of line(s) into which the given text is wrapped
* to.
*/
@ -134,7 +236,9 @@
lines = [],
line = '';
if (this._measureText(ctx, text, lineIndex) < maxWidth) {
var offset = 0;
if (this._measureText(ctx, text, lineIndex, offset) < maxWidth) {
lines.push(text);
}
else {
@ -153,7 +257,7 @@
* This handles a word that is longer than the width of the
* text area.
*/
while (Math.ceil(this._measureText(ctx, words[0], lineIndex)) >= maxWidth) {
while (Math.ceil(this._measureText(ctx, words[0], lineIndex, offset)) >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
@ -165,11 +269,11 @@
}
}
if (Math.ceil(this._measureText(ctx, line + words[0], lineIndex)) < maxWidth) {
if (Math.ceil(this._measureText(ctx, line + words[0], lineIndex, offset)) < maxWidth) {
line += words.shift() + ' ';
}
else {
lineIndex++;
offset += line.length;
lines.push(line);
line = '';
}