mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-09 22:34:43 +00:00
Use this.styles as if it's not affected by line-wraps.
This commit is contained in:
parent
4843a61a84
commit
9eb597c729
4 changed files with 338 additions and 50 deletions
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 = '';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue