diff --git a/src/gradient.class.js b/src/gradient.class.js
index bc6cb3e8..3f3b9cfb 100644
--- a/src/gradient.class.js
+++ b/src/gradient.class.js
@@ -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);
diff --git a/src/mixins/itext.svg_export.js b/src/mixins/itext.svg_export.js
index 7e234d11..b95d3be6 100644
--- a/src/mixins/itext.svg_export.js
+++ b/src/mixins/itext.svg_export.js
@@ -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
'
Supported mouse/touch combination
@@ -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 ); diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 05f6a94a..9e61fddb 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -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( - '