I have override fabricjs Text object to make letter spacing
fabric.util.object.extend(fabric.Text.prototype, {
letterSpace: 0,
_renderChars: function(method, ctx, chars, left, top) {
if(!this.letterSpace){
ctx[method](chars, left, top);
return;
}
var charShift = 0;
for(var i = 0; i < chars.length; i++){
if(i > 0){
charShift += this.letterSpace + ctx.measureText(chars.charAt(i-1)).width;
}
ctx[method](chars.charAt(i), left+charShift, top);
}
},
_getLineWidth: function(ctx, lineIndex) {
if (this.__lineWidths[lineIndex]) {
return this.__lineWidths[lineIndex];
}
var lineLength = this._textLines[lineIndex].length;
var additionalSpaceSum = 0
if(lineLength > 0){
additionalSpaceSum = this.letterSpace * (lineLength - 1);
}
this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width + additionalSpaceSum;
return this.__lineWidths[lineIndex];
}
});
Spacing works good, but width is not correct, how to improve width calculation?
I have improve my code in this question and now it work fine))
Sorry for not show previous mistake, but here is too difficult to make clear cod, and I have correct previous in this question.
But I have write it for left text align, if you use different align you need correct it. For me it was enough
I have improve my code in question and now it work fine)) sorry.
But I have write it for left text align, if you use different align you need correct it. For me it was enough
fabric.util.object.extend(fabric.Text.prototype, {
letterSpace: 0,
_renderChars: function (method, ctx, chars, left, top) {
if (!this.letterSpace) {
ctx[method](chars, left, top);
return;
}
var charShift = 0;
for (var i = 0; i < chars.length; i++) {
if (i > 0) {
charShift += this.letterSpace + ctx.measureText(chars.charAt(i - 1)).width;
}
ctx[method](chars.charAt(i), left + charShift, top);
}
},
_getLineWidth: function (ctx, lineIndex) {
var lineLength = this._textLines[lineIndex].length;
var additionalSpaceSum = 0
if (lineLength > 0) {
additionalSpaceSum = this.letterSpace * (lineLength - 1);
}
this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width + additionalSpaceSum;
return this.__lineWidths[lineIndex];
},
_renderExtended: function (ctx) {
this.clipTo && fabric.util.clipContext(this, ctx);
this.extendedRender = true;
this._renderTextBackground(ctx);
this._renderText(ctx);
this._renderTextDecoration(ctx);
this.clipTo && ctx.restore();
}
});
This code works for all alignments:
fabric.util.object.extend(fabric.Text.prototype, {
letterSpace: 0,
_renderChars: function (method, ctx, chars, left, top) {
if (!this.letterSpace) {
ctx[method](chars, left, top);
return;
}
var characters = String.prototype.split.call(chars, '');
if(this.textAlign == 'left'){
var charShift = 0;
for (var i = 0; i < chars.length; i++) {
if (i > 0) {
charShift += this.letterSpace + ctx.measureText(chars.charAt(i - 1)).width;
}
ctx[method](chars.charAt(i), left + charShift, top);
}
}else if(this.textAlign == 'right'){
characters.reverse();
chars = characters.join('');
var charShift = 0;
for (var i = 0; i < chars.length; i++) {
if (i > 0) {
charShift += this.letterSpace + ctx.measureText(chars.charAt(i - 1)).width;
}
ctx[method](chars.charAt(i), left - charShift, top);
}
}else if(this.textAlign == 'center'){
var totalWidth = 0;
for (var i = 0; i < characters.length; i++) {
totalWidth += (ctx.measureText(characters[i]).width + this.letterSpace);
}
var currentPosition = left - (totalWidth / 2);
var charShift = 0;
for (var i = 0; i < chars.length; i++) {
if (i > 0) {
charShift += this.letterSpace + ctx.measureText(chars.charAt(i - 1)).width;
}
ctx[method](chars.charAt(i), currentPosition + left + charShift, top);
}
}
},
_getLineWidth: function (ctx, lineIndex) {
var lineLength = this._textLines[lineIndex].length;
var additionalSpaceSum = 0
if (lineLength > 0) {
additionalSpaceSum = this.letterSpace * (lineLength - 1);
}
this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width + additionalSpaceSum;
return this.__lineWidths[lineIndex];
},
_renderExtended: function (ctx) {
this.clipTo && fabric.util.clipContext(this, ctx);
this.extendedRender = true;
this._renderTextBackground(ctx);
this._renderText(ctx);
this._renderTextDecoration(ctx);
this.clipTo && ctx.restore();
}});
Here is an example: http://jsfiddle.net/peybdq94/
I tried implementing this feature and came up with this https://jsfiddle.net/ghazaltaimur/bx0f4qpg/1/ by extending _renderChar. I have made a couple of additions. The code allows letter spacing to be applied on the selected text instead of only on the whole object.Plus itext selection ,bounding box and cursor position have to be taken into account if letter spacing is to be added. I have tried to cover these aspects as well. There might be a couple of issues which still need to be fixed.
fabric.util.object.extend(fabric.IText.prototype, {
letterSpace: 0,
_renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
var decl, charWidth, charHeight,
offset = this._fontSizeFraction * lineHeight / this.lineHeight;
if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
var shouldStroke = decl.stroke || this.stroke,
shouldFill = decl.fill || this.fill;
ctx.save();
charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl);
charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
var chars = _char;
var characters = String.prototype.split.call(chars, '');
var charShift = 0;
var leftcharShift = 0;
var letterSpace;
for (var i = 0; i < chars.length; i++) {
var style = this.getCurrentCharStyle(lineIndex, i + 1);
letterSpace = style.letterSpace;
if (i > 0) {
charShift += parseInt(letterSpace) + parseInt(ctx.measureText(chars.charAt(i - 1)).width);
}
if (this.text.indexOf(chars) !== 0 && charShift === 0) {
charShift = this.text.indexOf(chars) * parseInt(letterSpace);
}
leftcharShift = parseInt(left) + parseInt(charShift);
if (shouldFill) {
ctx.fillText(chars.charAt(i), leftcharShift, top);
}
if (shouldStroke) {
ctx.strokeText(chars.charAt(i), leftcharShift, top);
}
}
this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight);
ctx.restore();
ctx.translate(charWidth, 0);
} else {
charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
var chars = _char;
var characters = String.prototype.split.call(chars, '');
var charShift = 0;
var leftcharShift = 0;
var letterSpace;
for (var i = 0; i < chars.length; i++) {
var style = this.getCurrentCharStyle(lineIndex, i + 1);
letterSpace = style.letterSpace;
if (i > 0) {
charShift += parseInt(letterSpace) + parseInt(ctx.measureText(chars.charAt(i - 1)).width);
}
if (this.text.indexOf(chars) !== 0 && charShift === 0) {
charShift = this.text.indexOf(chars) * parseInt(letterSpace);
}
leftcharShift = parseInt(left) + parseInt(charShift);
if (method === 'strokeText' && this.stroke) {
ctx[method](chars.charAt(i), leftcharShift, top);
}
if (method === 'fillText' && this.fill) {
ctx[method](chars.charAt(i), leftcharShift, top);
}
}
this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize);
ctx.translate(ctx.measureText(_char).width, 0);
}},
getCurrentCharStyle: function(lineIndex, charIndex) {
var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)];
return {
fontSize: style && style.fontSize || this.fontSize,
fill: style && style.fill || this.fill,
textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,
textDecoration: style && style.textDecoration || this.textDecoration,
fontFamily: style && style.fontFamily || this.fontFamily,
fontWeight: style && style.fontWeight || this.fontWeight,
fontStyle: style && style.fontStyle || this.fontStyle,
stroke: style && style.stroke || this.stroke,
strokeWidth: style && style.strokeWidth || this.strokeWidth,
letterSpace: style && style.letterSpace || this.letterSpace
};
},
_renderTextLine: function(method, ctx, line, left, top, lineIndex) {
// to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
// the adding 0.05 is just to align text with itext by overlap test
if (!this.isEmptyStyles()) {
top += this.fontSize * (this._fontSizeFraction + 0.05);
}
this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
},
_getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
if (this.textAlign === 'justify' && /\s/.test(_char)) {
return this._getWidthOfSpace(ctx, lineIndex);
}
var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);
this._applyFontStyles(styleDeclaration);
var cacheProp = this._getCacheProp(_char, styleDeclaration);
var style = this.getCurrentCharStyle(lineIndex, charIndex);
var letterSpace = style.letterSpace;
if (this._charWidthsCache[cacheProp] && this.caching) {
return parseInt(this._charWidthsCache[cacheProp]) + parseInt(letterSpace);
} else if (ctx) {
ctx.save();
var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
width = parseInt(width) + parseInt(letterSpace);
ctx.restore();
return width;
}
},
});
// fabric js code
var canvas = new fabric.Canvas('fabric_canvas');
// add text
fabric.util.addListener(document.getElementById('addText'), 'click', function() {
var itext = new fabric.IText("Add text", {
left: 50,
top: 50
});
canvas.add(itext);
canvas.setActiveObject(itext);
var obj = canvas.getActiveObject();
var seletedText = obj.getSelectedText();
itext.selectAll();
itext.enterEditing();
if (obj.setSelectionStyles && obj.isEditing)
obj.setSelectionStyles({
letterSpace: 1
});
if (seletedText === "") {
obj.exitEditing();
}
canvas.renderAll();
});
// add letter spacing
fabric.util.addListener(document.getElementById('addLetterSpacing'), 'click', function() {
var activeObject = canvas.getActiveObject();
var seletedText = activeObject.getSelectedText();
if (seletedText === "") {
activeObject.selectAll();
activeObject.enterEditing();
}
if (activeObject.setSelectionStyles && activeObject.isEditing)
activeObject.setSelectionStyles({
letterSpace: 30
});
if (seletedText === "") {
activeObject.exitEditing();
}
var ctx = activeObject.ctx;
var textLines = activeObject.text.split(activeObject._reNewline);
var letterSpace = (activeObject.getSelectionStyles && activeObject.isEditing && activeObject.evented === true) ? activeObject.getSelectionStyles()["letterSpace"] : activeObject["letterSpace"];
activeObject.width = activeObject._getTextWidth(ctx, textLines, activeObject) + (activeObject.text.length * letterSpace);
activeObject.height = activeObject._getTextHeight(ctx, textLines, activeObject);
activeObject.callSuper('setCoords');
canvas.renderAll();
});
</code></pre>
[1]: https://jsfiddle.net/ghazaltaimur/bx0f4qpg/1/