diff --git a/CanvasTextWrapper.js b/CanvasTextWrapper.js new file mode 100644 index 0000000..5520ac3 --- /dev/null +++ b/CanvasTextWrapper.js @@ -0,0 +1,172 @@ +/*! 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 + }; + + 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 canvas = this.canvas; + var context = canvas.getContext('2d'); + + var elementWidth = (this.fitParent === false) ? canvas.width : canvas.parentNode.clientWidth; + var maxTextLength = elementWidth - (this.paddingX * 2); + + var words = this.text.split(/\s+/); + var lines = []; + var textPos = { + x: 0, + y: 0 + }; + + this.checkWordsLength(context, words, maxTextLength); + this.breakTextIntoLines(context, lines, words, maxTextLength); + + // height of the broken down into lines text + 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); + } + }, + + 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".'); + } + } + }; +})(); \ No newline at end of file diff --git a/CanvasTextWrapper.min.js b/CanvasTextWrapper.min.js new file mode 100644 index 0000000..f016061 --- /dev/null +++ b/CanvasTextWrapper.min.js @@ -0,0 +1,7 @@ +/*! 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 a={font:"18px Arial, sans-serif",textAlign:"left",verticalAlign:"top",paddingX:0,paddingY:0,fitParent:!1,lineBreak:"auto"};window.CanvasTextWrapper=function(b,c,d){if(!(this instanceof CanvasTextWrapper))throw new TypeError('CanvasTextWrapper constructor failed. Use "new" keyword when instantiating.');this.canvas=b,this.text=c;for(var e in a)this[e]=d&&d[e]?d[e]:a[e];this.lineHeight=parseInt(this.font.replace(/^\D+/g,""),10),this.validate(),this.context=this.canvas.getContext("2d"),this.context.font=this.font,this.context.textBaseline="bottom",this.drawText()},CanvasTextWrapper.prototype={drawText:function(){var a=this.canvas,b=a.getContext("2d"),c=this.fitParent===!1?a.width:a.parentNode.clientWidth,d=c-2*this.paddingX,e=this.text.split(/\s+/),f=[],g={x:0,y:0};this.checkWordsLength(b,e,d),this.breakTextIntoLines(b,f,e,d);var h=f.length*this.lineHeight;this.setTextVerticalAlign(g,h);for(var i=0;ic){for(var g=0;a.measureText(e+b[d][g]).width<=c&&g (https://github.com/namniak)", + "bugs": { + "url": "https://github.com/namniak/CanvasTextWrapper/issues" + } +}