Update to text, rendering and optimization

This commit is contained in:
Andrea Bogazzi 2014-11-30 19:04:25 +01:00 committed by asturur
parent 2fbebb03cc
commit 0e09961c64
11 changed files with 393 additions and 685 deletions

View file

@ -241,14 +241,14 @@
* @return {CanvasGradient}
*/
toLive: function(ctx, object) {
var gradient, coords = fabric.util.object.clone(this.coords);
var gradient, prop, coords = fabric.util.object.clone(this.coords);
if (!this.type) {
return;
}
if (object.group && object.group.type === 'path-group') {
for (var prop in coords) {
for (prop in coords) {
if (prop === 'x1' || prop === 'x2') {
coords[prop] += -this.offsetX + object.width / 2;
}
@ -258,6 +258,17 @@
}
}
if (object.type === 'text') {
for (prop in coords) {
if (prop === 'x1' || prop === 'x2') {
coords[prop] -= object.width / 2;
}
else if (prop === 'y1' || prop === 'y2') {
coords[prop] -= object.height / 2;
}
}
}
if (this.type === 'linear') {
gradient = ctx.createLinearGradient(
coords.x1, coords.y1, coords.x2, coords.y2);

View file

@ -4,27 +4,26 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
/**
* @private
*/
_setSVGTextLineText: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) {
_setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {
if (!this.styles[lineIndex]) {
this.callSuper('_setSVGTextLineText',
textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier);
lineIndex, textSpans, height, textLeftOffset, textTopOffset);
}
else {
this._setSVGTextLineChars(
textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects);
lineIndex, textSpans, height, textLeftOffset, textBgRects);
}
},
/**
* @private
*/
_setSVGTextLineChars: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) {
_setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) {
var yProp = lineIndex === 0 || this.useNative ? 'y' : 'dy',
chars = textLine.split(''),
var chars = this._textLines[lineIndex].split(''),
charOffset = 0,
lineLeftOffset = this._getSVGLineLeftOffset(lineIndex),
lineTopOffset = this._getSVGLineTopOffset(lineIndex),
lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2,
lineOffset = this._getSVGLineTopOffset(lineIndex),
heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);
for (var i = 0, len = chars.length; i < len; i++) {
@ -32,14 +31,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
textSpans.push(
this._createTextCharSpan(
chars[i], styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset));
chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));
var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);
if (styleDecl.textBackgroundColor) {
textBgRects.push(
this._createTextCharBg(
styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset));
styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));
}
charOffset += charWidth;
@ -50,20 +49,22 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
* @private
*/
_getSVGLineLeftOffset: function(lineIndex) {
return (this._boundaries && this._boundaries[lineIndex])
? fabric.util.toFixed(this._boundaries[lineIndex].left, 2)
: 0;
return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2);
},
/**
* @private
*/
_getSVGLineTopOffset: function(lineIndex) {
var lineTopOffset = 0;
for (var j = 0; j <= lineIndex; j++) {
var lineTopOffset = 0, lastHeight = 0;
for (var j = 0; j < lineIndex; j++) {
lineTopOffset += this._getHeightOfLine(this.ctx, j);
}
return lineTopOffset - this.height / 2;
lastHeight = this._getHeightOfLine(this.ctx, j);
return {
lineTop: lineTopOffset,
offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)
};
},
/**
@ -73,13 +74,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
return [
//jscs:disable validateIndentation
'<rect fill="', styleDecl.textBackgroundColor,
'" transform="translate(',
-this.width / 2, ' ',
-this.height + heightOfLine, ')',
'" x="', lineLeftOffset + charOffset,
'" y="', lineTopOffset + heightOfLine,
'" y="', lineTopOffset - this.height/2,
'" width="', charWidth,
'" height="', heightOfLine,
'" height="', heightOfLine / this.lineHeight,
'"></rect>'
//jscs:enable validateIndentation
].join('');
@ -88,7 +86,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
/**
* @private
*/
_createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset) {
_createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {
var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
visible: true,
@ -99,16 +97,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
return [
//jscs:disable validateIndentation
'<tspan x="', lineLeftOffset + charOffset, '" ',
yProp, '="', lineTopOffset, '" ',
'<tspan x="', lineLeftOffset + charOffset, '" y="',
lineTopOffset - this.height/2, '" ',
(styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ': ''),
(styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ': ''),
(styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ': ''),
(styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ': ''),
(styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ': ''),
'style="', fillStyles, '">',
fabric.util.string.escapeXml(_char),
'</tspan>'
//jscs:enable validateIndentation

View file

@ -157,10 +157,8 @@
* Selects entire text
*/
selectAll: function() {
this.selectionStart = 0;
this.selectionEnd = this.text.length;
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
this.setSelectionStart(0);
this.setSelectionEnd(this.text.length);
},
/**
@ -375,6 +373,7 @@
this.hiddenTextarea.value = this.text;
this.hiddenTextarea.selectionStart = this.selectionStart;
this.hiddenTextarea.selectionEnd = this.selectionEnd;
},
/**
@ -441,9 +440,8 @@
* @private
*/
_removeExtraneousStyles: function() {
var textLines = this.text.split(this._reNewline);
for (var prop in this.styles) {
if (!textLines[prop]) {
if (!this._textLines[prop]) {
delete this.styles[prop];
}
}
@ -474,13 +472,14 @@
this.text = this.text.slice(0, start) +
this.text.slice(end);
this._clearCache();
},
/**
* Inserts a character where cursor is (replacing selection if one exists)
* @param {String} _chars Characters to insert
*/
insertChars: function(_chars) {
insertChars: function(_chars, useCopiedStyle) {
var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n';
this.text = this.text.slice(0, this.selectionStart) +
@ -488,21 +487,16 @@
this.text.slice(this.selectionEnd);
if (this.selectionStart === this.selectionEnd) {
this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles);
this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle);
}
// else if (this.selectionEnd - this.selectionStart > 1) {
// TODO: replace styles properly
// console.log('replacing MORE than 1 char');
// }
this.selectionStart += _chars.length;
this.selectionEnd = this.selectionStart;
if (this.canvas) {
// TODO: double renderAll gets rid of text box shift happenning sometimes
// need to find out what exactly causes it and fix it
this.canvas.renderAll().renderAll();
}
this.setSelectionStart(this.selectionStart + _chars.length);
this.setSelectionEnd(this.selectionStart);
this._clearCache();
this.canvas && this.canvas.renderAll();
this.setCoords();
this.fire('changed');
@ -544,6 +538,7 @@
}
this.styles[lineIndex + 1] = newLineStyles;
}
this._clearCache();
},
/**
@ -573,15 +568,16 @@
this.styles[lineIndex][charIndex] =
style || clone(currentLineStyles[charIndex - 1]);
this._clearCache();
},
/**
* Inserts style object(s)
* @param {String} _chars Characters at the location where style is inserted
* @param {Boolean} isEndOfLine True if it's end of line
* @param {Array} [styles] Styles to insert
* @param {Boolean} [useCopiedStyle] Style to insert
*/
insertStyleObjects: function(_chars, isEndOfLine, styles) {
insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) {
// removed shortcircuit over isEmptyStyles
var cursorLocation = this.get2DCursorLocation(),
@ -596,8 +592,8 @@
this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
}
else {
if (styles) {
this._insertStyles(styles);
if (useCopiedStyle) {
this._insertStyles(this.copiedStyles);
}
else {
// TODO: support multiple style insertion if _chars.length > 1
@ -649,8 +645,7 @@
if (isBeginningOfLine) {
var textLines = this.text.split(this._reNewline),
textOnPreviousLine = textLines[lineIndex - 1],
var textOnPreviousLine = this._textLines[lineIndex - 1],
newCharIndexOnPrevLine = textOnPreviousLine
? textOnPreviousLine.length
: 0;

View file

@ -203,30 +203,30 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
*/
getSelectionStartFromPointer: function(e) {
var mouseOffset = this._getLocalRotatedPointer(e),
textLines = this.text.split(this._reNewline),
prevWidth = 0,
width = 0,
height = 0,
charIndex = 0,
newSelectionStart;
for (var i = 0, len = textLines.length; i < len; i++) {
newSelectionStart,
line;
for (var i = 0, len = this._textLines.length; i < len; i++) {
line = this._textLines[i].split('');
height += this._getHeightOfLine(this.ctx, i) * this.scaleY;
var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines),
var widthOfLine = this._getLineWidth(this.ctx, i),
lineLeftOffset = this._getLineLeftOffset(widthOfLine);
width = lineLeftOffset * this.scaleX;
if (this.flipX) {
// when oject is horizontally flipped we reverse chars
textLines[i] = textLines[i].split('').reverse().join('');
this._textLines[i] = line.split('').reverse().join('');
}
for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
for (var j = 0, jlen = line.length; j < jlen; j++) {
var _char = textLines[i][j];
var _char = line[j];
prevWidth = width;
width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) *

View file

@ -62,7 +62,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
if (!this.isEditing) {
return;
}
if (e.keyCode in this._keysMap) {
this[this._keysMap[e.keyCode]](e);
}
@ -72,10 +71,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
else {
return;
}
e.stopImmediatePropagation();
e.preventDefault();
this.canvas && this.canvas.renderAll();
},
@ -125,7 +122,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
}
if (copiedText) {
this.insertChars(copiedText);
this.insertChars(copiedText, true);
}
},
@ -173,10 +170,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
*/
getDownCursorOffset: function(e, isRight) {
var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
textLines = this.text.split(this._reNewline),
_char,
lineLeftOffset,
_char, lineLeftOffset,
textBeforeCursor = this.text.slice(0, selectionProp),
textAfterCursor = this.text.slice(selectionProp),
@ -187,13 +181,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
cursorLocation = this.get2DCursorLocation(selectionProp);
// if on last line, down cursor goes to end of line
if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey || e.keyCode === 34) {
if (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {
// move to the end of a text
return this.text.length - selectionProp;
}
var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines);
var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex);
lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor);
var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
@ -205,7 +199,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
}
var indexOnNextLine = this._getIndexOnNextLine(
cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines);
cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor);
return textOnSameLineAfterCursor.length + 1 + indexOnNextLine;
},
@ -213,9 +207,9 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
/**
* @private
*/
_getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) {
_getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) {
var lineIndex = cursorLocation.lineIndex + 1,
widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines),
widthOfNextLine = this._getLineWidth(this.ctx, lineIndex),
lineLeftOffset = this._getLineLeftOffset(widthOfNextLine),
widthOfCharsOnNextLine = lineLeftOffset,
indexOnNextLine = 0,
@ -277,15 +271,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
*/
moveCursorDownWithoutShift: function(offset) {
this._selectionDirection = 'right';
this.selectionStart += offset;
if (this.selectionStart > this.text.length) {
this.selectionStart = this.text.length;
}
this.selectionEnd = this.selectionStart;
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
this.setSelectionStart(this.selectionStart + offset);
this.setSelectionEnd(this.selectionStart);
},
/**
@ -293,8 +280,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
*/
swapSelectionPoints: function() {
var swapSel = this.selectionEnd;
this.selectionEnd = this.selectionStart;
this.selectionStart = swapSel;
this.setSelectionEnd(this.selectionStart);
this.setSelectionStart(swapSel);
},
/**
@ -305,18 +292,19 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
if (this.selectionEnd === this.selectionStart) {
this._selectionDirection = 'right';
}
var prop = this._selectionDirection === 'right' ? 'selectionEnd' : 'selectionStart';
this[prop] += offset;
if (this._selectionDirection === 'right') {
this.setSelectionEnd(this.selectionEnd + offset);
}
else {
this.setSelectionStart(this.selectionStart + offset);
}
if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') {
this.swapSelectionPoints();
this._selectionDirection = 'right';
}
if (this.selectionEnd > this.text.length) {
this.selectionEnd = this.text.length;
this.setSelectionEnd(this.text.length);
}
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
@ -335,9 +323,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
var textBeforeCursor = this.text.slice(0, selectionProp),
textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),
textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '',
textLines = this.text.split(this._reNewline),
_char,
widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines),
widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex),
lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor),
widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
lineIndex = cursorLocation.lineIndex;
@ -348,7 +335,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
}
var indexOnPrevLine = this._getIndexOnPrevLine(
cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines);
cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor);
return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length;
},
@ -356,10 +343,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
/**
* @private
*/
_getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) {
_getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) {
var lineIndex = cursorLocation.lineIndex - 1,
widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines),
widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex),
lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine),
widthOfCharsOnPreviousLine = lineLeftOffset,
indexOnPrevLine = 0,
@ -423,18 +410,16 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
if (this.selectionEnd === this.selectionStart) {
this._selectionDirection = 'left';
}
var prop = this._selectionDirection === 'right' ? 'selectionEnd' : 'selectionStart';
this[prop] -= offset;
if (this._selectionDirection === 'right') {
this.setSelectionEnd(this.selectionEnd - offset);
}
else {
this.setSelectionStart(this.selectionStart - offset);
}
if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') {
this.swapSelectionPoints();
this._selectionDirection = 'left';
}
if (this.selectionStart < 0) {
this.selectionStart = 0;
}
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
@ -443,17 +428,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
*/
moveCursorUpWithoutShift: function(offset) {
if (this.selectionStart === this.selectionEnd) {
this.selectionStart -= offset;
this.setSelectionStart(this.selectionStart - offset);
}
if (this.selectionStart < 0) {
this.selectionStart = 0;
}
this.selectionEnd = this.selectionStart;
this.setSelectionEnd(this.selectionStart);
this._selectionDirection = 'left';
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
@ -482,14 +461,15 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
* @private
*/
_move: function(e, prop, direction) {
var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd');
if (e.altKey) {
this[prop] = this['findWordBoundary' + direction](this[prop]);
this[propMethod](this['findWordBoundary' + direction](this[prop]));
}
else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) {
this[prop] = this['findLineBoundary' + direction](this[prop]);
this[propMethod](this['findLineBoundary' + direction](this[prop]));
}
else {
this[prop] += (direction === 'Left' ? -1 : 1);
this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1));
}
},
@ -519,10 +499,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
if (this.selectionEnd === this.selectionStart) {
this._moveLeft(e, 'selectionStart');
}
this.selectionEnd = this.selectionStart;
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
this.setSelectionEnd(this.selectionStart);
},
/**
@ -539,15 +516,9 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
// increase selection by one if it's a newline
if (this.text.charAt(this.selectionStart) === '\n') {
this.selectionStart--;
}
if (this.selectionStart < 0) {
this.selectionStart = 0;
this.setSelectionStart(this.selectionStart - 1);
}
}
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
@ -586,15 +557,9 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
// increase selection by one if it's a newline
if (this.text.charAt(this.selectionEnd - 1) === '\n') {
this.selectionEnd++;
}
if (this.selectionEnd > this.text.length) {
this.selectionEnd = this.text.length;
this.setSelectionEnd(this.selectionEnd + 1);
}
}
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
@ -606,22 +571,16 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
if (this.selectionStart === this.selectionEnd) {
this._moveRight(e, 'selectionStart');
this.selectionEnd = this.selectionStart;
this.setSelectionEnd(this.selectionStart);
}
else {
this.selectionEnd += this.getNumNewLinesInSelectedText();
if (this.selectionEnd > this.text.length) {
this.selectionEnd = this.text.length;
}
this.selectionStart = this.selectionEnd;
this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText());
this.setSelectionStart(this.selectionEnd);
}
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
},
/**
* Inserts a character where cursor is (replacing selection if one exists)
* Removes characters selected by selection
* @param {Event} e Event object
*/
removeChars: function(e) {
@ -632,15 +591,12 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
}
this.selectionEnd = this.selectionStart;
this.setSelectionEnd(this.selectionStart);
this._removeExtraneousStyles();
if (this.canvas) {
// TODO: double renderAll gets rid of text box shift happenning sometimes
// need to find out what exactly causes it and fix it
this.canvas.renderAll().renderAll();
}
this._clearCache();
this.canvas && this.canvas.renderAll();
this.setCoords();
this.fire('changed');
@ -659,20 +615,19 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);
this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
this.selectionStart = leftLineBoundary;
this.setSelectionStart(leftLineBoundary);
}
else if (e.altKey) {
// remove all till the start of current word
var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);
this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
this.selectionStart = leftWordBoundary;
this.setSelectionStart(leftWordBoundary);
}
else {
var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n';
this.removeStyleObject(isBeginningOfLine);
this.selectionStart--;
this.setSelectionStart(this.selectionStart - 1);
this.text = this.text.slice(0, this.selectionStart) +
this.text.slice(this.selectionStart + 1);
}

View file

@ -23,7 +23,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1',
visibility = this.visible ? '' : ' visibility: hidden;',
filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
filter = this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
return [
'stroke: ', stroke, '; ',

View file

@ -24,10 +24,10 @@
* Select text vertically: shift + up, shift + down
* Move cursor by word: alt + left, alt + right
* Select words: shift + alt + left, shift + alt + right
* Move cursor to line start/end: cmd + left, cmd + right
* Select till start/end of line: cmd + shift + left, cmd + shift + right
* Move cursor to line start/end: cmd + left, cmd + right or home, end
* Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end
* Jump to start/end of text: cmd + up, cmd + down
* Select till start/end of text: cmd + shift + up, cmd + shift + down
* Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
* Delete character: backspace
* Delete word: alt + backspace
* Delete line: cmd + backspace
@ -36,6 +36,7 @@
* Paste text: ctrl/cmd + v
* Cut text: ctrl/cmd + x
* Select entire text: ctrl/cmd + a
* Quit editing tab or esc
* </pre>
*
* <p>Supported mouse/touch combination</p>
@ -146,18 +147,13 @@
* @type Boolean
* @default
*/
_skipFillStrokeCheck: true,
_skipFillStrokeCheck: false,
/**
* @private
*/
_reSpace: /\s|\n/,
/**
* @private
*/
_fontSizeFraction: 4,
/**
* @private
*/
@ -191,10 +187,14 @@
fabric.IText.instances.push(this);
// caching
this.__lineWidths = { };
this.__lineHeights = { };
this.__lineOffsets = { };
},
/**
* @private
*/
_clearCache: function() {
this.callSuper('_clearCache');
this.__maxFontHeights = [ ];
},
/**
@ -222,12 +222,13 @@
* @param {Number} index Index to set selection start to
*/
setSelectionStart: function(index) {
index = Math.max(index, 0);
if (this.selectionStart !== index) {
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
this.selectionStart = index;
}
this.selectionStart = index;
this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index);
this._updateTextarea();
},
/**
@ -235,12 +236,13 @@
* @param {Number} index Index to set selection end to
*/
setSelectionEnd: function(index) {
index = Math.min(index, this.text.length);
if (this.selectionEnd !== index) {
this.fire('selection:changed');
this.canvas && this.canvas.fire('text:selection:changed', { target: this });
this.selectionEnd = index;
}
this.selectionEnd = index;
this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index);
this._updateTextarea();
},
/**
@ -405,18 +407,14 @@
*/
_getCursorBoundaries: function(chars, typeOfBoundaries) {
var cursorLocation = this.get2DCursorLocation(),
textLines = this.text.split(this._reNewline),
// left/top are left/top of entire text box
// leftOffset/topOffset are offset from that left/top point of a text box
left = Math.round(this._getLeftOffset()),
var left = Math.round(this._getLeftOffset()),
top = this._getTopOffset(),
offsets = this._getCursorBoundariesOffsets(
chars, typeOfBoundaries, cursorLocation, textLines);
chars, typeOfBoundaries);
return {
left: left,
@ -429,26 +427,19 @@
/**
* @private
*/
_getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) {
_getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
var lineLeftOffset = 0,
lineIndex = 0,
charIndex = 0,
leftOffset = 0,
topOffset = typeOfBoundaries === 'cursor'
// selection starts at the very top of the line,
// whereas cursor starts at the padding created by line height
? (this._getHeightOfLine(this.ctx, 0) -
this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex))
: 0;
topOffset = 0,
leftOffset = 0;
for (var i = 0; i < this.selectionStart; i++) {
if (chars[i] === '\n') {
leftOffset = 0;
var index = lineIndex + (typeOfBoundaries === 'cursor' ? 1 : 0);
topOffset += this._getCachedLineHeight(index);
topOffset += this._getHeightOfLine(this.ctx, lineIndex);
lineIndex++;
charIndex = 0;
@ -458,10 +449,12 @@
charIndex++;
}
lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines);
lineLeftOffset = this._getCachedLineOffset(lineIndex);
}
if (typeOfBoundaries === 'cursor') {
topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight
- this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);
}
this._clearCache();
return {
top: topOffset,
@ -473,33 +466,8 @@
/**
* @private
*/
_clearCache: function() {
this.__lineWidths = { };
this.__lineHeights = { };
this.__lineOffsets = { };
},
/**
* @private
*/
_getCachedLineHeight: function(index) {
return this.__lineHeights[index] ||
(this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index));
},
/**
* @private
*/
_getCachedLineWidth: function(lineIndex, textLines) {
return this.__lineWidths[lineIndex] ||
(this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines));
},
/**
* @private
*/
_getCachedLineOffset: function(lineIndex, textLines) {
var widthOfLine = this._getCachedLineWidth(lineIndex, textLines);
_getCachedLineOffset: function(lineIndex) {
var widthOfLine = this._getLineWidth(this.ctx, lineIndex);
return this.__lineOffsets[lineIndex] ||
(this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine));
@ -549,30 +517,29 @@
var start = this.get2DCursorLocation(this.selectionStart),
end = this.get2DCursorLocation(this.selectionEnd),
startLine = start.lineIndex,
endLine = end.lineIndex,
textLines = this.text.split(this._reNewline);
endLine = end.lineIndex;
for (var i = startLine; i <= endLine; i++) {
var lineOffset = this._getCachedLineOffset(i, textLines) || 0,
lineHeight = this._getCachedLineHeight(i),
boxWidth = 0;
var lineOffset = this._getCachedLineOffset(i) || 0,
lineHeight = this._getHeightOfLine(this.ctx, i),
boxWidth = 0, line = this._textLines[i];
if (i === startLine) {
for (var j = 0, len = textLines[i].length; j < len; j++) {
for (var j = 0, len = line.length; j < len; j++) {
if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j);
boxWidth += this._getWidthOfChar(ctx, line[j], i, j);
}
if (j < start.charIndex) {
lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j);
lineOffset += this._getWidthOfChar(ctx, line[j], i, j);
}
}
}
else if (i > startLine && i < endLine) {
boxWidth += this._getCachedLineWidth(i, textLines) || 5;
boxWidth += this._getLineWidth(ctx, i) || 5;
}
else if (i === endLine) {
for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2);
boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);
}
}
@ -608,10 +575,8 @@
: 0;
// set proper line offset
var textLines = this.text.split(this._reNewline),
lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines),
lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines),
lineLeftOffset = this._getLineLeftOffset(lineWidth),
var lineHeight = this._getHeightOfLine(ctx, lineIndex),
lineLeftOffset = this._getCachedLineOffset(lineIndex),
chars = line.split(''),
prevStyle,
charsToRender = '';
@ -619,7 +584,7 @@
left += lineLeftOffset || 0;
ctx.save();
top -= lineHeight / this.lineHeight * this._fontSizeFraction;
for (var i = 0, len = chars.length; i <= len; i++) {
prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);
@ -649,7 +614,7 @@
if (method === 'fillText' && this.fill) {
this.callSuper('_renderChars', method, ctx, line, left, top);
}
if (method === 'strokeText' && this.stroke) {
if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {
this.callSuper('_renderChars', method, ctx, line, left, top);
}
},
@ -666,7 +631,8 @@
* @param {Number} lineHeight Height of the line
*/
_renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
var decl, charWidth, charHeight;
var decl, charWidth, charHeight,
offset = this._fontSizeFraction * lineHeight / this.lineHeight;
if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
@ -684,7 +650,7 @@
ctx.strokeText(_char, left, top);
}
this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight, charHeight);
this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight);
ctx.restore();
ctx.translate(charWidth, 0);
@ -697,7 +663,7 @@
ctx[method](_char, left, top);
}
charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight);
this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize);
ctx.translate(ctx.measureText(_char).width, 0);
}
@ -725,58 +691,42 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) {
_renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) {
var textDecoration = styleDeclaration
? (styleDeclaration.textDecoration || this.textDecoration)
: this.textDecoration,
fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize;
: this.textDecoration;
if (!textDecoration) {
return;
}
if (textDecoration.indexOf('underline') > -1) {
this._renderCharDecorationAtOffset(
ctx,
ctx.fillRect(
left,
top + (this.fontSize / this._fontSizeFraction),
charWidth,
0,
this.fontSize / 20
top + charHeight / 10,
charWidth ,
charHeight / 15
);
}
if (textDecoration.indexOf('line-through') > -1) {
this._renderCharDecorationAtOffset(
ctx,
ctx.fillRect(
left,
top + (this.fontSize / this._fontSizeFraction),
top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15,
charWidth,
charHeight / 2,
fontSize / 20
charHeight / 15
);
}
if (textDecoration.indexOf('overline') > -1) {
this._renderCharDecorationAtOffset(
ctx,
ctx.fillRect(
left,
top,
top - (this._fontSizeMult - this._fontSizeFraction) * charHeight,
charWidth,
lineHeight - (this.fontSize / this._fontSizeFraction),
this.fontSize / 20
charHeight / 15
);
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset, thickness) {
ctx.fillRect(left, top - offset, charWidth, thickness);
},
/**
* @private
* @param {String} method
@ -785,27 +735,26 @@
*/
_renderTextLine: function(method, ctx, line, left, top, lineIndex) {
// to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
top += this.fontSize / 4;
// the adding 0.03 is just to align text with itext by overlap test
top += this.fontSize * (this._fontSizeFraction + 0.03);
this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines
*/
_renderTextDecoration: function(ctx, textLines) {
_renderTextDecoration: function(ctx) {
if (this.isEmptyStyles()) {
return this.callSuper('_renderTextDecoration', ctx, textLines);
return this.callSuper('_renderTextDecoration', ctx);
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextLinesBackground: function(ctx, textLines) {
_renderTextLinesBackground: function(ctx) {
if (!this.textBackgroundColor && !this.styles) {
return;
}
@ -816,43 +765,42 @@
ctx.fillStyle = this.textBackgroundColor;
}
var lineHeights = 0,
fractionOfFontSize = this.fontSize / this._fontSizeFraction;
var lineHeights = 0;
for (var i = 0, len = textLines.length; i < len; i++) {
for (var i = 0, len = this._textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
if (textLines[i] === '') {
var heightOfLine = this._getHeightOfLine(ctx, i);
if (this._textLines[i] === '') {
lineHeights += heightOfLine;
continue;
}
var lineWidth = this._getWidthOfLine(ctx, i, textLines),
lineLeftOffset = this._getLineLeftOffset(lineWidth);
var lineWidth = this._getLineWidth(ctx, i),
lineLeftOffset = this._getCachedLineOffset(i);
if (this.textBackgroundColor) {
ctx.fillStyle = this.textBackgroundColor;
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset,
this._getTopOffset() + lineHeights + fractionOfFontSize,
this._getTopOffset() + lineHeights,
lineWidth,
heightOfLine
heightOfLine / this.lineHeight
);
}
if (this.styles[i]) {
for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
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 _char = textLines[i][j];
var _char = this._textLines[i][j];
ctx.fillStyle = this.styles[i][j].textBackgroundColor;
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines),
this._getTopOffset() + lineHeights + fractionOfFontSize,
this._getWidthOfChar(ctx, _char, i, j, textLines) + 1,
heightOfLine
this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),
this._getTopOffset() + lineHeights,
this._getWidthOfChar(ctx, _char, i, j) + 1,
heightOfLine / this.lineHeight
);
}
}
@ -867,12 +815,10 @@
*/
_getCacheProp: function(_char, styleDeclaration) {
return _char +
styleDeclaration.fontFamily +
styleDeclaration.fontSize +
styleDeclaration.fontWeight +
styleDeclaration.fontStyle +
styleDeclaration.shadow;
},
@ -1005,9 +951,8 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) {
lines = lines || this.text.split(this._reNewline);
var _char = lines[lineIndex].split('')[charIndex];
_getWidthOfCharAt: function(ctx, lineIndex, charIndex) {
var _char = this._textLines[lineIndex].split('')[charIndex];
return this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
},
@ -1015,9 +960,8 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) {
lines = lines || this.text.split(this._reNewline);
var _char = lines[lineIndex].split('')[charIndex];
_getHeightOfCharAt: function(ctx, lineIndex, charIndex) {
var _char = this._textLines[lineIndex].split('')[charIndex];
return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);
},
@ -1025,10 +969,10 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) {
_getWidthOfCharsAt: function(ctx, lineIndex, charIndex) {
var width = 0;
for (var i = 0; i < charIndex; i++) {
width += this._getWidthOfCharAt(ctx, lineIndex, i, lines);
width += this._getWidthOfCharAt(ctx, lineIndex, i);
}
return width;
},
@ -1037,11 +981,12 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getWidthOfLine: function(ctx, lineIndex, textLines) {
// if (!this.styles[lineIndex]) {
// return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]);
// }
return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines);
_getLineWidth: function(ctx, lineIndex) {
if (this.__lineWidths[lineIndex]) {
return this.__lineWidths[lineIndex];
}
this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);
return this.__lineWidths[lineIndex];
},
/**
@ -1085,33 +1030,13 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getTextWidth: function(ctx, textLines) {
if (this.isEmptyStyles()) {
return this.callSuper('_getTextWidth', ctx, textLines);
_getHeightOfLine: function(ctx, lineIndex) {
if (this.__lineHeights[lineIndex]) {
return this.__lineHeights[lineIndex];
}
var maxWidth = this._getWidthOfLine(ctx, 0, textLines);
for (var i = 1, len = textLines.length; i < len; i++) {
var currentLineWidth = this._getWidthOfLine(ctx, i, textLines);
if (currentLineWidth > maxWidth) {
maxWidth = currentLineWidth;
}
}
return maxWidth;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_getHeightOfLine: function(ctx, lineIndex, textLines) {
textLines = textLines || this.text.split(this._reNewline);
var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0),
line = textLines[lineIndex],
var line = this._textLines[lineIndex],
maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0),
chars = line.split('');
for (var i = 1, len = chars.length; i < len; i++) {
@ -1120,31 +1045,23 @@
maxHeight = currentCharHeight;
}
}
return maxHeight * this.lineHeight;
this.__maxFontHeights[lineIndex] = maxHeight;
this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
return this.__lineHeights[lineIndex];
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_getTextHeight: function(ctx, textLines) {
_getTextHeight: function(ctx) {
var height = 0;
for (var i = 0, len = textLines.length; i < len; i++) {
height += this._getHeightOfLine(ctx, i, textLines);
for (var i = 0, len = this._textLines.length; i < len; i++) {
height += this._getHeightOfLine(ctx, i);
}
return height;
},
/**
* @private
*/
_getTopOffset: function() {
var topOffset = fabric.Text.prototype._getTopOffset.call(this);
return topOffset - (this.fontSize / this._fontSizeFraction);
},
/**
* This method is overwritten to account for different top offset
* @private
@ -1159,7 +1076,7 @@
ctx.fillRect(
this._getLeftOffset(),
this._getTopOffset() + (this.fontSize / this._fontSizeFraction),
this._getTopOffset(),
this.width,
this.height
);

View file

@ -23,9 +23,7 @@
'textAlign',
'fontStyle',
'lineHeight',
'textBackgroundColor',
'useNative',
'path'
'textBackgroundColor'
);
/**
@ -258,7 +256,7 @@
* @type Number
* @default
*/
lineHeight: 1.3,
lineHeight: 1.16,
/**
* Background color of text lines
@ -267,20 +265,6 @@
*/
textBackgroundColor: '',
/**
* URL of a font file, when using Cufon
* @type String | null
* @default
*/
path: null,
/**
* Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used)
* @type Boolean
* @default
*/
useNative: true,
/**
* List of properties to consider when checking if
* state of an object is changed ({@link fabric.Object#hasStateChanged})
@ -305,6 +289,18 @@
*/
shadow: null,
/**
* @private
*/
_fontSizeFraction: 0.25,
/**
* Text Line proportion to font Size (in pixels)
* @type Number
* @default
*/
_fontSizeMult: 1.13,
/**
* Constructor
* @param {String} text Text string
@ -313,7 +309,6 @@
*/
initialize: function(text, options) {
options = options || { };
this.text = text;
this.__skipDimension = true;
this.setOptions(options);
@ -329,8 +324,13 @@
if (this.__skipDimension) {
return;
}
var canvasEl = fabric.util.createCanvasElement();
this._render(canvasEl.getContext('2d'));
this._clearCache();
var ctx = fabric.util.createCanvasElement().getContext('2d');
this._textLines = this.text.split(this._reNewline);
this._setTextStyles(ctx);
this.width = this._getTextWidth(ctx);
this.height = this._getTextHeight(ctx);
},
/**
@ -348,54 +348,31 @@
*/
_render: function(ctx) {
if (typeof Cufon === 'undefined' || this.useNative === true) {
this._renderViaNative(ctx);
}
else {
this._renderViaCufon(ctx);
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderViaNative: function(ctx) {
var textLines = this.text.split(this._reNewline);
this._setTextStyles(ctx);
this.width = this._getTextWidth(ctx, textLines);
this.height = this._getTextHeight(ctx, textLines);
this.clipTo && fabric.util.clipContext(this, ctx);
this._renderTextBackground(ctx, textLines);
this._renderTextBackground(ctx);
this._translateForTextAlign(ctx);
this._renderText(ctx, textLines);
this._renderText(ctx);
if (this.textAlign !== 'left' && this.textAlign !== 'justify') {
ctx.restore();
}
this._renderTextDecoration(ctx, textLines);
this._renderTextDecoration(ctx);
this.clipTo && ctx.restore();
this._setBoundaries(ctx, textLines);
this._totalLineHeight = 0;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderText: function(ctx, textLines) {
_renderText: function(ctx) {
ctx.save();
this._setOpacity(ctx);
this._setShadow(ctx);
this._setupCompositeOperation(ctx);
this._renderTextFill(ctx, textLines);
this._renderTextStroke(ctx, textLines);
this._renderTextFill(ctx);
this._renderTextStroke(ctx);
this._restoreCompositeOperation(ctx);
this._removeShadow(ctx);
ctx.restore();
@ -412,34 +389,11 @@
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_setBoundaries: function(ctx, textLines) {
this._boundaries = [ ];
for (var i = 0, len = textLines.length; i < len; i++) {
var lineWidth = this._getLineWidth(ctx, textLines[i]),
lineLeftOffset = this._getLineLeftOffset(lineWidth);
this._boundaries.push({
height: this.fontSize * this.lineHeight,
width: lineWidth,
left: lineLeftOffset
});
}
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_setTextStyles: function(ctx) {
this._setFillStyles(ctx);
this._setStrokeStyles(ctx);
ctx.textBaseline = 'alphabetic';
if (!this.skipTextAlign) {
ctx.textAlign = this.textAlign;
@ -450,24 +404,22 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
* @return {Number} Height of fabric.Text object
*/
_getTextHeight: function(ctx, textLines) {
return this.fontSize * textLines.length * this.lineHeight;
_getTextHeight: function() {
return this._textLines.length * this._getHeightOfLine();
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
* @return {Number} Maximum width of fabric.Text object
*/
_getTextWidth: function(ctx, textLines) {
var maxWidth = ctx.measureText(textLines[0] || '|').width;
_getTextWidth: function(ctx) {
var maxWidth = this._getLineWidth(ctx, 0);
for (var i = 1, len = textLines.length; i < len; i++) {
var currentLineWidth = ctx.measureText(textLines[i]).width;
for (var i = 1, len = this._textLines.length; i < len; i++) {
var currentLineWidth = this._getLineWidth(ctx, i);
if (currentLineWidth > maxWidth) {
maxWidth = currentLineWidth;
}
@ -498,7 +450,7 @@
*/
_renderTextLine: function(method, ctx, line, left, top, lineIndex) {
// lift the line by quarter of fontSize
top -= this.fontSize / 4;
top -= this.fontSize * this._fontSizeFraction;
// short-circuit
if (this.textAlign !== 'justify') {
@ -506,7 +458,7 @@
return;
}
var lineWidth = ctx.measureText(line).width,
var lineWidth = this._getLineWidth(ctx, i),
totalWidth = this.width;
if (totalWidth > lineWidth) {
@ -547,28 +499,27 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextFill: function(ctx, textLines) {
_renderTextFill: function(ctx) {
if (!this.fill && !this._skipFillStrokeCheck) {
return;
}
this._boundaries = [ ];
var lineHeights = 0;
for (var i = 0, len = textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
lineHeights += heightOfLine;
for (var i = 0, len = this._textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i),
maxHeight = heightOfLine / this.lineHeight;
this._renderTextLine(
'fillText',
ctx,
textLines[i],
this._textLines[i],
this._getLeftOffset(),
this._getTopOffset() + lineHeights,
this._getTopOffset() + lineHeights + maxHeight,
i
);
lineHeights += heightOfLine;
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
@ -578,9 +529,8 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextStroke: function(ctx, textLines) {
_renderTextStroke: function(ctx) {
if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) {
return;
}
@ -588,6 +538,7 @@
var lineHeights = 0;
ctx.save();
if (this.strokeDashArray) {
// Spec requires the concatenation of two copies the dash list when the number of elements is odd
if (1 & this.strokeDashArray.length) {
@ -597,25 +548,26 @@
}
ctx.beginPath();
for (var i = 0, len = textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
lineHeights += heightOfLine;
for (var i = 0, len = this._textLines.length; i < len; i++) {
var heightOfLine = this._getHeightOfLine(ctx, i),
maxHeight = heightOfLine / this.lineHeight;
this._renderTextLine(
'strokeText',
ctx,
textLines[i],
this._textLines[i],
this._getLeftOffset(),
this._getTopOffset() + lineHeights,
this._getTopOffset() + lineHeights + maxHeight,
i
);
lineHeights += heightOfLine;
}
ctx.closePath();
ctx.restore();
},
_getHeightOfLine: function() {
return this.fontSize * this.lineHeight;
return this.fontSize * this._fontSizeMult * this.lineHeight;
},
/**
@ -623,9 +575,9 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextBackground: function(ctx, textLines) {
_renderTextBackground: function(ctx) {
this._renderTextBoxBackground(ctx);
this._renderTextLinesBackground(ctx, textLines);
this._renderTextLinesBackground(ctx);
},
/**
@ -653,9 +605,9 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
*/
_renderTextLinesBackground: function(ctx, textLines) {
_renderTextLinesBackground: function(ctx) {
var lineTopOffset = 0, heightOfLine = this._getHeightOfLine();
if (!this.textBackgroundColor) {
return;
}
@ -663,20 +615,21 @@
ctx.save();
ctx.fillStyle = this.textBackgroundColor;
for (var i = 0, len = textLines.length; i < len; i++) {
for (var i = 0, len = this._textLines.length; i < len; i++) {
if (textLines[i] !== '') {
if (this._textLines[i] !== '') {
var lineWidth = this._getLineWidth(ctx, textLines[i]),
var lineWidth = this._getLineWidth(ctx, i),
lineLeftOffset = this._getLineLeftOffset(lineWidth);
ctx.fillRect(
this._getLeftOffset() + lineLeftOffset,
this._getTopOffset() + (i * this.fontSize * this.lineHeight),
this._getTopOffset() + lineTopOffset,
lineWidth,
this.fontSize * this.lineHeight
this.fontSize * this._fontSizeMult
);
}
lineTopOffset += heightOfLine;
}
ctx.restore();
},
@ -698,53 +651,85 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {String} line Text line
* @return {Number} Line width
*/
_getLineWidth: function(ctx, line) {
return this.textAlign === 'justify'
? this.width
: ctx.measureText(line).width;
_clearCache: function() {
this.__lineWidths = [ ];
this.__lineHeights = [ ];
this.__lineOffsets = [ ];
},
/**
* @private
*/
_shouldClearCache: function() {
var shouldClear = false;
for (var prop in this._dimensionAffectingProps) {
if (this['__' + prop] !== this[prop]) {
this['__' + prop] = this[prop];
shouldClear = true;
}
}
return shouldClear;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Array} textLines Array of all text lines
* @return {Number} Line width
*/
_renderTextDecoration: function(ctx, textLines) {
_getLineWidth: function(ctx, lineIndex) {
if (this.__lineWidths[lineIndex]) {
return this.__lineWidths[lineIndex];
}
this.__lineWidths[lineIndex] = this.textAlign === 'justify' ?
this.width : ctx.measureText(this._textLines[lineIndex]).width;
return this.__lineWidths[lineIndex];
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderTextDecoration: function(ctx) {
if (!this.textDecoration) {
return;
}
// var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2;
var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2,
_this = this;
var halfOfVerticalBox = this.height / 2,
_this = this, offsets = [];
/** @ignore */
function renderLinesAtOffset(offset) {
for (var i = 0, len = textLines.length; i < len; i++) {
function renderLinesAtOffset(offsets) {
var i, lineHeight = 0, len, j, oLen;
for (i = 0, len = _this._textLines.length; i < len; i++) {
var lineWidth = _this._getLineWidth(ctx, textLines[i]),
lineLeftOffset = _this._getLineLeftOffset(lineWidth);
var lineWidth = _this._getLineWidth(ctx, i),
lineLeftOffset = _this._getLineLeftOffset(lineWidth),
heightOfLine = _this._getHeightOfLine(ctx, i);
ctx.fillRect(
_this._getLeftOffset() + lineLeftOffset,
~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox),
lineWidth,
1);
for (j = 0, oLen = offsets.length; j < oLen; j++) {
ctx.fillRect(
_this._getLeftOffset() + lineLeftOffset,
lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox,
lineWidth,
_this.fontSize / 15);
}
lineHeight += heightOfLine;
}
}
if (this.textDecoration.indexOf('underline') > -1) {
renderLinesAtOffset(this.fontSize * this.lineHeight);
offsets.push(0.85); // 1 - 3/16
}
if (this.textDecoration.indexOf('line-through') > -1) {
renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2);
offsets.push(0.43);
}
if (this.textDecoration.indexOf('overline') > -1) {
renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize);
offsets.push(-0.12);
}
if (offsets.length > 0) {
renderLinesAtOffset(offsets);
}
},
@ -772,10 +757,19 @@
}
ctx.save();
this._setTextStyles(ctx);
if (this._shouldClearCache()) {
this._clearCache();
this._textLines = this.text.split(this._reNewline);
this.width = this._getTextWidth(ctx);
this.height = this._getTextHeight(ctx);
}
if (!noTransform) {
this.transform(ctx);
}
this._setStrokeStyles(ctx);
this._setFillStyles(ctx);
var isInPathGroup = this.group && this.group.type === 'path-group';
if (isInPathGroup) {
@ -806,9 +800,7 @@
lineHeight: this.lineHeight,
textDecoration: this.textDecoration,
textAlign: this.textAlign,
path: this.path,
textBackgroundColor: this.textBackgroundColor,
useNative: this.useNative
textBackgroundColor: this.textBackgroundColor
});
if (!this.includeDefaultValues) {
this._removeDefaultValues(object);
@ -823,16 +815,10 @@
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
var markup = [ ],
textLines = this.text.split(this._reNewline),
offsets = this._getSVGLeftTopOffsets(textLines),
textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines),
shadowSpans = this._getSVGShadows(offsets.lineTop, textLines);
// move top offset by an ascent
offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0);
this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets);
var markup = this._createBaseSVGMarkup(),
offsets = this._getSVGLeftTopOffsets(this.ctx),
textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
this._wrapSVGTextAndBg(markup, textAndBg);
return reviver ? reviver(markup.join('')) : markup.join('');
},
@ -840,19 +826,14 @@
/**
* @private
*/
_getSVGLeftTopOffsets: function(textLines) {
var lineTop = this.useNative
? this.fontSize * this.lineHeight
: (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)),
textLeft = -(this.width/2),
textTop = this.useNative
? (this.fontSize * this.lineHeight - 0.25 * this.fontSize) // to lift by 1 / 4 of font height.
: (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight;
_getSVGLeftTopOffsets: function(ctx) {
var lineTop = this._getHeightOfLine(ctx, 0),
textLeft = -this.width / 2,
textTop = 0;
return {
textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),
textTop: textTop + (this.group && this.group.type === 'path-group' ? this.top : 0),
textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),
lineTop: lineTop
};
},
@ -860,99 +841,43 @@
/**
* @private
*/
_wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) {
_wrapSVGTextAndBg: function(markup, textAndBg) {
markup.push(
'<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
'\t<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
textAndBg.textBgRects.join(''),
'<text ',
'\t\t<text ',
(this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ': ''),
(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 */
'transform="translate(', toFixed(offsets.textLeft, 2), ' ', toFixed(offsets.textTop, 2), ')">',
shadowSpans.join(''),
'style="', this.getSvgStyles(), '" >',
textAndBg.textSpans.join(''),
'</text>\n',
'</g>\n'
'\t</g>\n'
);
},
/**
* @private
* @param {Number} lineHeight
* @param {Array} textLines Array of all text lines
* @return {Array}
*/
_getSVGShadows: function(lineHeight, textLines) {
var shadowSpans = [],
i, len,
lineTopOffsetMultiplier = 1;
if (!this.shadow || !this._boundaries) {
return shadowSpans;
}
for (i = 0, len = textLines.length; i < len; i++) {
if (textLines[i] !== '') {
var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0;
shadowSpans.push(
'<tspan x="',
toFixed((lineLeftOffset + lineTopOffsetMultiplier) + this.shadow.offsetX, 2),
((i === 0 || this.useNative) ? '" y' : '" dy'), '="',
toFixed(this.useNative
? ((lineHeight * i) - this.height / 2 + this.shadow.offsetY)
: (lineHeight + (i === 0 ? this.shadow.offsetY : 0)), 2),
'" ',
this._getFillAttributes(this.shadow.color), '>',
fabric.util.string.escapeXml(textLines[i]),
'</tspan>');
lineTopOffsetMultiplier = 1;
}
else {
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
// prevents empty tspans
lineTopOffsetMultiplier++;
}
}
return shadowSpans;
},
/**
* @private
* @param {Number} lineHeight
* @param {Number} textTopOffset Text top offset
* @param {Number} textLeftOffset Text left offset
* @param {Array} textLines Array of all text lines
* @return {Object}
*/
_getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) {
_getSVGTextAndBg: function(textTopOffset, textLeftOffset) {
var textSpans = [ ],
textBgRects = [ ],
lineTopOffsetMultiplier = 1;
height = 0;
// bounding-box background
this._setSVGBg(textBgRects);
// text and text-background
for (var i = 0, len = textLines.length; i < len; i++) {
if (textLines[i] !== '') {
this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects);
lineTopOffsetMultiplier = 1;
for (var i = 0, len = this._textLines.length; i < len; i++) {
if (this.textBackgroundColor) {
this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);
}
else {
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
// prevents empty tspans
lineTopOffsetMultiplier++;
}
if (!this.textBackgroundColor || !this._boundaries) {
continue;
}
this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight);
this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);
height += this._getHeightOfLine(this.ctx, i);
}
return {
@ -961,56 +886,52 @@
};
},
_setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) {
var lineLeftOffset = (this._boundaries && this._boundaries[i])
? toFixed(this._boundaries[i].left, 2)
: 0;
_setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) {
var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)
- textTopOffset + height - this.height / 2;
textSpans.push(
'<tspan x="',
lineLeftOffset, '" ',
(i === 0 || this.useNative ? 'y' : 'dy'), '="',
toFixed(this.useNative
? ((lineHeight * i) - this.height / 2)
: (lineHeight * lineTopOffsetMultiplier), 2), '" ',
toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4), '" ',
'y="',
toFixed(yPos, 4),
'" ',
// doing this on <tspan> elements since setting opacity
// on containing <text> one doesn't work in Illustrator
this._getFillAttributes(this.fill), '>',
fabric.util.string.escapeXml(textLine),
fabric.util.string.escapeXml(this._textLines[i]),
'</tspan>'
);
},
_setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) {
_setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) {
textBgRects.push(
'<rect ',
'\t\t<rect ',
this._getFillAttributes(this.textBackgroundColor),
' x="',
toFixed(textLeftOffset + this._boundaries[i].left, 2),
toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4),
'" y="',
/* an offset that seems to straighten things out */
toFixed((lineHeight * i) - this.height / 2, 2),
toFixed(height - this.height / 2, 4),
'" width="',
toFixed(this._boundaries[i].width, 2),
toFixed(this.__lineWidths[i], 4),
'" height="',
toFixed(this._boundaries[i].height, 2),
toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, 4),
'"></rect>\n');
},
_setSVGBg: function(textBgRects) {
if (this.backgroundColor && this._boundaries) {
if (this.backgroundColor) {
textBgRects.push(
'<rect ',
'\t\t<rect ',
this._getFillAttributes(this.backgroundColor),
' x="',
toFixed(-this.width / 2, 2),
toFixed(-this.width / 2, 4),
'" y="',
toFixed(-this.height / 2, 2),
toFixed(-this.height / 2, 4),
'" width="',
toFixed(this.width, 2),
toFixed(this.width, 4),
'" height="',
toFixed(this.height, 2),
'"></rect>');
toFixed(this.height, 4),
'"></rect>\n');
}
},
@ -1039,9 +960,6 @@
* @chainable
*/
_set: function(key, value) {
if (key === 'fontFamily' && this.path) {
this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3');
}
this.callSuper('_set', key, value);
if (key in this._dimensionAffectingProps) {
@ -1107,7 +1025,6 @@
if (!options.originX) {
options.originX = 'left';
}
options.top += options.fontSize / 4;
var text = new fabric.Text(element.textContent, options),
/*
Adjust positioning:
@ -1124,7 +1041,7 @@
}
text.set({
left: text.getLeft() + offX,
top: text.getTop() - text.getHeight() / 2
top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */
});
return text;

View file

@ -1,79 +0,0 @@
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
fabric.util.object.extend(fabric.Text.prototype, {
_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.shadow && this.shadow.toString(),
textAlign: this.textAlign,
fontStyle: this.fontStyle,
lineHeight: this.lineHeight,
stroke: this.stroke,
strokeWidth: this.strokeWidth,
backgroundColor: this.backgroundColor,
textBackgroundColor: this.textBackgroundColor
});
// update width, height
this.width = o.width;
this.height = o.height;
this._totalLineHeight = o.totalLineHeight;
this._fontAscent = o.fontAscent;
this._boundaries = o.boundaries;
el = null;
// need to set coords _after_ the width/height was retreived from Cufon
this.setCoords();
},
/**
* @private
*/
_initDummyElementForCufon: function() {
var el = fabric.document.createElement('pre'),
container = fabric.document.createElement('div');
// Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent
container.appendChild(el);
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
if (typeof G_vmlCanvasManager === 'undefined') {
el.innerHTML = this.text;
}
//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
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
el.innerText = this.text.replace(/\r?\n/gi, '\r');
}
el.style.fontSize = this.fontSize + 'px';
el.style.letterSpacing = 'normal';
return el;
}
});

View file

@ -9,7 +9,7 @@
'left': 0,
'top': 0,
'width': 20,
'height': 52,
'height': 58.76,
'fill': 'rgb(0,0,0)',
'stroke': null,
'strokeWidth': 1,
@ -34,10 +34,8 @@
'lineHeight': 1.3,
'textDecoration': '',
'textAlign': 'left',
'path': null,
'backgroundColor': '',
'textBackgroundColor': '',
'useNative': true,
'fillRule': 'nonzero',
'globalCompositeOperation': 'source-over',
styles: { }

View file

@ -15,7 +15,7 @@
'left': 0,
'top': 0,
'width': CHAR_WIDTH,
'height': 52,
'height': 52.43,
'fill': 'rgb(0,0,0)',
'stroke': null,
'strokeWidth': 1,
@ -38,17 +38,15 @@
'fontWeight': 'normal',
'fontFamily': 'Times New Roman',
'fontStyle': '',
'lineHeight': 1.3,
'lineHeight': 1.16,
'textDecoration': '',
'textAlign': 'left',
'path': null,
'textBackgroundColor': '',
'useNative': true,
'fillRule': 'nonzero',
'globalCompositeOperation': 'source-over'
};
var TEXT_SVG = '<g transform="translate(10 26)">\n<text font-family="Times New Roman" font-size="40" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform="translate(-10 42)"><tspan x="0" y="-26" fill="rgb(0,0,0)">x</tspan></text>\n</g>\n';
var TEXT_SVG = '\t<g transform="translate(10 26.22)">\n\t\t<text font-family="Times New Roman" font-size="40" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" ><tspan x="-10" y="8.984" fill="rgb(0,0,0)">x</tspan></text>\n\t</g>\n';
test('constructor', function() {
ok(fabric.Text);
@ -156,9 +154,9 @@
var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), {
left: 4,
top: -6.4,
top: -3.61,
width: 8,
height: 20.8,
height: 20.97,
fontSize: 16,
originX: 'left'
});
@ -197,9 +195,9 @@
var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), {
/* left varies slightly due to node-canvas rendering */
left: fabric.util.toFixed(textWithAttrs.left + '', 2),
top: -29.2,
top: -7.72,
width: CHAR_WIDTH,
height: 159.9,
height: 161.23,
fill: 'rgb(255,255,255)',
opacity: 0.45,
stroke: 'blue',