/*! CanvasTextWrapper * https://github.com/namniak/CanvasTextWrapper * Version: 0.3.0 * MIT License (http://www.opensource.org/licenses/mit-license.html) * Copyright (c) 2014 Vadim Namniak */ (function() { 'use strict'; var EL_WIDTH,EL_HEIGHT,MAX_TXT_WIDTH,MAX_TXT_HEIGHT; var defaults = { font: '18px Arial, sans-serif', sizeToFill: false, // text is resized to fill the container (given font size is ignored) lineHeight: 1, // default line height equivalent of '100%' allowNewLine: true, // breaks text on every new line character '\n' lineBreak: 'auto', // text fills the element's (canvas or parent) width going to a new line on a whole word textAlign: 'left', // each line of text is aligned left verticalAlign: 'top', // text lines block is aligned top justifyLines: false, // lines are not justified paddingX: 0, // 0px left & right text padding relatively to canvas or its container paddingY: 0, // 0px top & bottom text padding relatively to canvas or its container fitParent: false, // text is set to fit canvas width strokeText: false // text is stroked according to context configuration }; var CanvasTextWrapper = function(canvas,text,options) { if (!(this instanceof CanvasTextWrapper)) { return new CanvasTextWrapper(canvas,text,options); } this.canvas = canvas; this.text = text; this.context = this.canvas.getContext('2d'); this.context.font = this.font; this.context.textBaseline = 'bottom'; for (var property in defaults) { if (defaults.hasOwnProperty(property)) { this[property] = (options && options[property]) ? options[property] : defaults[property]; } } EL_WIDTH = (this.fitParent === false) ? this.canvas.width : this.canvas.parentNode.clientWidth; EL_HEIGHT = (this.fitParent === false) ? this.canvas.height : this.canvas.parentNode.clientHeight; MAX_TXT_WIDTH = EL_WIDTH - (this.paddingX * 2); MAX_TXT_HEIGHT = EL_HEIGHT - (this.paddingY * 2); this._init(); }; CanvasTextWrapper.prototype = { _init: function() { this.fontSize = parseInt(this.font.replace(/^\D+/g,''),10) || 18; this.textBlockHeight = 0; this.lines = []; this.newLineIndexes = []; this.textPos = {x: 0,y: 0}; this._setFont(this.fontSize); this._setLineHeight(); this._validate(); this._render(); }, _render: function() { if (this.sizeToFill) { var numWords = this.text.trim().split(/\s+/).length; var fontSize = 0; do { this._setFont(++fontSize); this.lineHeight = this.fontSize; this._wrap(); } while (this.textBlockHeight < MAX_TXT_HEIGHT && (this.lines.join(' ').split(/\s+/).length == numWords)); this._setFont(--fontSize); this.lineHeight = this.fontSize; } else { this._wrap(); } if (this.justifyLines && this.lineBreak === 'auto') { this._justify(); } this._setAlignY(); this._drawText(); }, _setFont: function(fontSize) { var fontParts = (!this.sizeToFill) ? this.font.split(/\b\d+px\b/i) : this.context.font.split(/\b\d+px\b/i); this.context.font = fontParts[0] + fontSize + 'px' + fontParts[1]; this.fontSize = fontSize; }, _setLineHeight: function() { if (!isNaN(this.lineHeight)) { this.lineHeight = this.fontSize * this.lineHeight; } else if (this.lineHeight.toString().indexOf('px') !== -1) { this.lineHeight = parseInt(this.lineHeight); } else if (this.lineHeight.toString().indexOf('%') !== -1) { this.lineHeight = (parseInt(this.lineHeight) / 100) * this.fontSize; } }, _wrap: function() { if (this.allowNewLine) { var newLines = this.text.trim().split('\n'); for (var i = 0,idx = 0; i < newLines.length - 1; i++) { idx += newLines[i].trim().split(/\s+/).length; this.newLineIndexes.push(idx) } } var words = this.text.trim().split(/\s+/); this._checkLength(words); this._breakText(words); this.textBlockHeight = this.lines.length * this.lineHeight; }, _checkLength: function(words) { var testString,tokenLen,sliced,leftover; for (var i = 0; i < words.length; i++) { testString = ''; tokenLen = this.context.measureText(words[i]).width; if (tokenLen > MAX_TXT_WIDTH) { for (var k = 0; (this.context.measureText(testString + words[i][k]).width <= MAX_TXT_WIDTH) && (k < words[i].length); k++) { testString += words[i][k]; } sliced = words[i].slice(0,k); leftover = words[i].slice(k); words.splice(i,1,sliced,leftover); } } }, _breakText: function(words) { for (var i = 0,j = 0; i < words.length; j++) { this.lines[j] = ''; if (this.lineBreak === 'auto') { while ((this.context.measureText(this.lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) { this.lines[j] += words[i] + ' '; i++; if (this.allowNewLine) { for (var k = 0; k < this.newLineIndexes.length; k++) { if (this.newLineIndexes[k] === i) { j++; this.lines[j] = ''; break; } } } } this.lines[j] = this.lines[j].trim(); } else { this.lines[j] = words[i]; i++; } } }, _justify: function() { var maxLen,longestLineIndex,tokenLen; for (var i = 0; i < this.lines.length; i++) { tokenLen = this.context.measureText(this.lines[i]).width; if (!maxLen || tokenLen > maxLen) { maxLen = tokenLen; longestLineIndex = i; } } // fill lines with extra spaces var numWords,spaceLength,numOfSpaces,num,filler; var delimiter = '\u200A'; for (i = 0; i < this.lines.length; i++) { if (i === longestLineIndex) continue; numWords = this.lines[i].trim().split(/\s+/).length; if (numWords <= 1) continue; this.lines[i] = this.lines[i].trim().split(/\s+/).join(delimiter); spaceLength = this.context.measureText(delimiter).width; numOfSpaces = (maxLen - this.context.measureText(this.lines[i]).width) / spaceLength; num = numOfSpaces / (numWords - 1); filler = ''; for (var j = 0; j < num; j++) { filler += delimiter; } this.lines[i] = this.lines[i].trim().split(delimiter).join(filler); //console.log('numWords:', numWords, 'numOfSpaces:', numOfSpaces, 'num:', num); } }, _drawText: function() { for (var i = 0; i < this.lines.length; i++) { this._setAlignX(this.lines[i]); this.textPos.y = parseInt(this.textPos.y) + this.lineHeight; this.context.fillText(this.lines[i],this.textPos.x,this.textPos.y); if (this.strokeText) { this.context.strokeText(this.lines[i],this.textPos.x,this.textPos.y); } } }, _setAlignX: function(line) { if (this.textAlign == 'center') { this.textPos.x = (EL_WIDTH - this.context.measureText(line).width) / 2; } else if (this.textAlign == 'right') { this.textPos.x = EL_WIDTH - this.context.measureText(line).width - this.paddingX; } else { this.textPos.x = this.paddingX; } }, _setAlignY: function() { if (this.verticalAlign == 'middle') { this.textPos.y = (EL_HEIGHT - this.textBlockHeight) / 2; } else if (this.verticalAlign == 'bottom') { this.textPos.y = EL_HEIGHT - this.textBlockHeight - this.paddingY; } else { this.textPos.y = this.paddingY; } }, _validate: function() { if (!(this.canvas instanceof HTMLCanvasElement)) throw new TypeError('The first parameter must be an instance of HTMLCanvasElement.'); if (typeof this.text !== 'string') throw new TypeError('The second parameter must be a string.'); if (isNaN(this.fontSize)) throw new TypeError('Cannot parse "font".'); if (isNaN(this.lineHeight)) throw new TypeError('Cannot parse "lineHeight".'); if (this.textAlign.toLocaleLowerCase() !== 'left' && this.textAlign.toLocaleLowerCase() !== 'center' && this.textAlign.toLocaleLowerCase() !== 'right') throw new TypeError('Property "textAlign" must be set to either "left", "center", or "right".'); if (this.verticalAlign.toLocaleLowerCase() !== 'top' && this.verticalAlign.toLocaleLowerCase() !== 'middle' && this.verticalAlign.toLocaleLowerCase() !== 'bottom') throw new TypeError('Property "verticalAlign" must be set to either "top", "middle", or "bottom".'); if (typeof this.justifyLines !== 'boolean') throw new TypeError('Property "justifyLines" must be set to a Boolean.'); if (isNaN(this.paddingX)) throw new TypeError('Property "paddingX" must be set to a Number.'); if (isNaN(this.paddingY)) throw new TypeError('Property "paddingY" must be set to a Number.'); if (typeof this.fitParent !== 'boolean') throw new TypeError('Property "fitParent" must be set to a Boolean.'); if (this.lineBreak.toLocaleLowerCase() !== 'auto' && this.lineBreak.toLocaleLowerCase() !== 'word') throw new TypeError('Property "lineBreak" must be set to either "auto" or "word".'); if (typeof this.sizeToFill !== 'boolean') throw new TypeError('Property "sizeToFill" must be set to a Boolean.'); if (typeof this.strokeText !== 'boolean') throw new TypeError('Property "strokeText" must be set to a Boolean.'); } }; window.CanvasTextWrapper = CanvasTextWrapper; })();