mirror of
https://github.com/wassname/CanvasTextWrapper.git
synced 2026-06-27 19:29:01 +08:00
200 lines
8.8 KiB
JavaScript
200 lines
8.8 KiB
JavaScript
/*! CanvasTextWrapper (https://github.com/namniak/CanvasTextWrapper)
|
|
* Version: 0.1.0
|
|
*
|
|
* MIT License (http://www.opensource.org/licenses/mit-license.html)
|
|
* Copyright (c) 2014 Vadim Namniak
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
var defaultOptions = {
|
|
font: '18px Arial, sans-serif',
|
|
textAlign: 'left', // each line of text is aligned left
|
|
verticalAlign: 'top', // text lines block is aligned top
|
|
paddingX: 0, // zero px left & right text padding relative to canvas or parent
|
|
paddingY: 0, // zero px top & bottom text padding relative to canvas or parent
|
|
fitParent: false, // text is tested to fit canvas width
|
|
lineBreak: 'auto', // text fills the element's (canvas or parent) width going to a new line on a whole word
|
|
sizeToFill: false // text is resized to fill the container height (given font size is ignored)
|
|
};
|
|
|
|
window.CanvasTextWrapper = function(canvas, text, opts) {
|
|
|
|
if (!(this instanceof CanvasTextWrapper)) {
|
|
throw new TypeError('CanvasTextWrapper constructor failed. Use "new" keyword when instantiating.');
|
|
}
|
|
|
|
this.canvas = canvas;
|
|
this.text = text;
|
|
|
|
// set options to specified or default values
|
|
for (var property in defaultOptions) {
|
|
this[property] = (opts && opts[property]) ? opts[property] : defaultOptions[property];
|
|
}
|
|
|
|
// extract font size
|
|
this.lineHeight = parseInt(this.font.replace(/^\D+/g, ''), 10);
|
|
|
|
// validate all set properties
|
|
this.validate();
|
|
|
|
// basic context settings
|
|
this.context = this.canvas.getContext('2d');
|
|
this.context.font = this.font;
|
|
this.context.textBaseline = 'bottom';
|
|
|
|
this.drawText();
|
|
};
|
|
|
|
CanvasTextWrapper.prototype = {
|
|
|
|
drawText: function() {
|
|
var elementWidth = (this.fitParent === false) ? this.canvas.width : this.canvas.parentNode.clientWidth;
|
|
var textPos = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
if (this.sizeToFill) {
|
|
// starting at 1px increase font size by 1px until text block exceeds the height of its padded container or until words break
|
|
var elementHeight = ((this.fitParent === false) ? this.canvas.height : this.canvas.parentNode.clientHeight) - (this.paddingX * 2);
|
|
var numWords = this.text.trim().split(/\s+/).length;
|
|
var fontSize = 0;
|
|
do {
|
|
this.setFontSize(++fontSize);
|
|
var lines = this.getWrappedText(elementWidth);
|
|
var textBlockHeight = lines.length * this.lineHeight;
|
|
} while (textBlockHeight < elementHeight && lines.join(' ').split(/\s+/).length == numWords);
|
|
|
|
// use previous font size, not the one that broke the while condition
|
|
this.setFontSize(--fontSize);
|
|
}
|
|
|
|
var lines = this.getWrappedText(elementWidth);
|
|
var textBlockHeight = lines.length * this.lineHeight;
|
|
|
|
// set vertical align for the whole text block
|
|
this.setTextVerticalAlign(textPos, textBlockHeight);
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
this.setTextHorizontalAlign(context, textPos, elementWidth, lines[i]);
|
|
|
|
textPos.y = parseInt(textPos.y) + parseInt(this.lineHeight);
|
|
context.fillText(lines[i], textPos.x, textPos.y);
|
|
}
|
|
},
|
|
|
|
setFontSize: function(size) {
|
|
var fontParts = this.context.font.split(/\b\d+px\b/i);
|
|
this.context.font = fontParts[0] + size + 'px' + fontParts[1];
|
|
this.lineHeight = size;
|
|
},
|
|
|
|
getWrappedText: function(elementWidth) {
|
|
var maxTextLength = elementWidth - (this.paddingX * 2);
|
|
|
|
var words = this.text.trim().split(/\s+/);
|
|
var lines = [];
|
|
|
|
this.checkWordsLength(this.context, words, maxTextLength);
|
|
this.breakTextIntoLines(this.context, lines, words, maxTextLength);
|
|
|
|
return lines;
|
|
},
|
|
|
|
checkWordsLength: function(context, words, maxTextLength) {
|
|
for (var i = 0; i < words.length; i++) {
|
|
var testString = '';
|
|
var tokenLen = context.measureText(words[i]).width;
|
|
|
|
// check if a word exceeds the element's width
|
|
if (tokenLen > maxTextLength) {
|
|
for (var k = 0; (context.measureText(testString + words[i][k]).width <= maxTextLength) && (k < words[i].length); k++) {
|
|
testString += words[i][k];
|
|
}
|
|
|
|
// break the word because it's too long
|
|
var sliced = words[i].slice(0, k);
|
|
var leftover = words[i].slice(k);
|
|
words.splice(i, 1, sliced, leftover);
|
|
}
|
|
}
|
|
},
|
|
|
|
breakTextIntoLines: function(context, lines, words, maxTextLength) {
|
|
for (var i = 0, j = 0; i < words.length; j++) {
|
|
lines[j] = '';
|
|
|
|
if (this.lineBreak === 'auto') {
|
|
// put as many full words in a line as can fit element
|
|
while ((context.measureText(lines[j] + words[i]).width <= maxTextLength) && (i < words.length)) {
|
|
lines[j] += words[i] + ' ';
|
|
i++;
|
|
}
|
|
lines[j] = lines[j].trim();
|
|
} else if (this.lineBreak === 'word') {
|
|
// put each next word in a new line
|
|
lines[j] = words[i];
|
|
i++;
|
|
}
|
|
}
|
|
},
|
|
|
|
setTextHorizontalAlign: function(context, textPos, elementWidth, line) {
|
|
if (this.textAlign === 'center') {
|
|
textPos.x = (elementWidth - context.measureText(line).width) / 2;
|
|
} else if (this.textAlign === 'right') {
|
|
textPos.x = elementWidth - context.measureText(line).width - this.paddingX;
|
|
} else {
|
|
textPos.x = this.paddingX;
|
|
}
|
|
},
|
|
|
|
setTextVerticalAlign: function(textPos, textBlockHeight) {
|
|
var elementHeight = (this.fitParent === false) ? this.canvas.height : this.canvas.parentNode.clientHeight;
|
|
|
|
if (this.verticalAlign === 'middle') {
|
|
textPos.y = (elementHeight - textBlockHeight) / 2;
|
|
} else if (this.verticalAlign === 'bottom') {
|
|
textPos.y = elementHeight - textBlockHeight - this.paddingY;
|
|
} else {
|
|
textPos.y = this.paddingY;
|
|
}
|
|
},
|
|
|
|
validate: function() {
|
|
if (!(this.canvas instanceof HTMLCanvasElement)) {
|
|
throw new TypeError('From CanvasTextWrapper(): Element passed as the first parameter is not an instance of HTMLCanvasElement.');
|
|
}
|
|
if (typeof this.text !== 'string') {
|
|
throw new TypeError('From CanvasTextWrapper(): The second, dedicated for the text, parameter must be a string.');
|
|
}
|
|
if (isNaN(this.lineHeight)) {
|
|
throw new TypeError('From CanvasTextWrapper(): Cannot parse font size as an Integer. Check "font" property\'s value.');
|
|
}
|
|
if (this.textAlign !== 'left' && this.textAlign !== 'center' && this.textAlign !== 'right') {
|
|
throw new TypeError('From CanvasTextWrapper(): Unsupported horizontal align value is used. Property "textAlign" can only be set to "left", "center", or "right".');
|
|
}
|
|
if (this.verticalAlign !== 'top' && this.verticalAlign !== 'middle' && this.verticalAlign !== 'bottom') {
|
|
throw new TypeError('From CanvasTextWrapper(): Unsupported vertical align value is used. Property "verticalAlign" can only be set to "top", "middle", or "bottom".');
|
|
}
|
|
if (isNaN(this.paddingX)) {
|
|
throw new TypeError('From CanvasTextWrapper(): Unsupported horizontal padding value is used. Property "paddingX" must be set to a number');
|
|
}
|
|
if (isNaN(this.paddingY)) {
|
|
throw new TypeError('From CanvasTextWrapper(): Unsupported vertical padding value is used. Property "paddingY" must be set to a number.');
|
|
}
|
|
if (typeof this.fitParent !== 'boolean') {
|
|
throw new TypeError('From CanvasTextWrapper(): Property "fitParent" must be set to a Boolean.');
|
|
}
|
|
if (this.lineBreak !== 'auto' && this.lineBreak !== 'word') {
|
|
throw new TypeError('From CanvasTextWrapper(): Unsupported line break value is used. Property "lineBreak" can only be set to "auto", or "word".');
|
|
}
|
|
if (typeof this.sizeToFill !== 'boolean') {
|
|
throw new TypeError('From CanvasTextWrapper(): Property "sizeToFill" must be set to a Boolean.');
|
|
}
|
|
}
|
|
};
|
|
})();
|