diff --git a/CanvasTextWrapper.js b/CanvasTextWrapper.js index bbce110..52ac03c 100644 --- a/CanvasTextWrapper.js +++ b/CanvasTextWrapper.js @@ -4,178 +4,169 @@ * MIT License (http://www.opensource.org/licenses/mit-license.html) */ -(function(root) { - 'use strict'; - var EL_WIDTH,EL_HEIGHT,MAX_TXT_WIDTH,MAX_TXT_HEIGHT; +(function (root) { - 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 - }; + function CanvasTextWrapper(canvas, text, opts) { + 'use strict'; - var CanvasTextWrapper = function(canvas,text,options) { - if (!(this instanceof CanvasTextWrapper)) { - return new CanvasTextWrapper(canvas,text,options); - } + 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 + }; - this.canvas = canvas; - this.text = text; - this.context = this.canvas.getContext('2d'); - this.context.font = this.font; - this.context.textBaseline = 'bottom'; + opts = opts || {}; for (var property in defaults) { if (defaults.hasOwnProperty(property)) { - this[property] = (options && options[property]) ? options[property] : defaults[property]; + opts[property] = (opts && opts[property]) ? opts[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); + var context = canvas.getContext('2d'); + context.font = opts.font; + context.textBaseline = 'bottom'; - this._init(); - }; + var EL_WIDTH = (opts.fitParent === false) ? canvas.width : canvas.parentNode.clientWidth; + var EL_HEIGHT = (opts.fitParent === false) ? canvas.height : canvas.parentNode.clientHeight; + var MAX_TXT_WIDTH = EL_WIDTH - (opts.paddingX * 2); + var MAX_TXT_HEIGHT = EL_HEIGHT - (opts.paddingY * 2); - CanvasTextWrapper.prototype = { - _init: function() { - /* Substituting this line that causing a bug with font-weight numeric values */ - //this.fontSize = parseInt(this.font.replace(/^\D+/g,''),10) || 18; + var fontSize, textBlockHeight, lines, newLineIndexes, textPos, lineHeight; - /* This line allow font values like : "italic 500 25px" to preserve both numeric a literal value of font-weight*/ - this.fontSize = this.font.match(/\d+(px|em|\%)/g) ? +this.font.match(/\d+(px|em|\%)/g)[0].match(/\d+/g) : 18; + init(); - this.textBlockHeight = 0; - this.lines = []; - this.newLineIndexes = []; - this.textPos = {x: 0,y: 0}; + function init() { + fontSize = opts.font.match(/\d+(px|em|%)/g) ? +opts.font.match(/\d+(px|em|%)/g)[0].match(/\d+/g) : 18; - this._setFont(this.fontSize); - this._setLineHeight(); - this._validate(); - this._render(); - }, + textBlockHeight = 0; + lines = []; + newLineIndexes = []; + textPos = {x: 0, y: 0}; - _render: function() { - if (this.sizeToFill) { - var numWords = this.text.trim().split(/\s+/).length; + setFont(fontSize); + setLineHeight(); + validate(); + render(); + } + + function render() { + if (opts.sizeToFill) { + var numWords = 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)); + setFont(++fontSize); + lineHeight = fontSize; + wrap(); + } while (textBlockHeight < MAX_TXT_HEIGHT && (lines.join(' ').split(/\s+/).length == numWords)); - this._setFont(--fontSize); - this.lineHeight = this.fontSize; + setFont(--fontSize); + lineHeight = fontSize; } else { - this._wrap(); + wrap(); } - if (this.justifyLines && this.lineBreak === 'auto') { - this._justify(); + if (opts.justifyLines && opts.lineBreak === 'auto') { + justify(); } - this._setAlignY(); - this._drawText(); - }, + setVerticalAlign(); + 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; - }, + function setFont(fontSize) { + var fontParts = (!opts.sizeToFill) ? opts.font.split(/\b\d+px\b/i) : context.font.split(/\b\d+px\b/i); + context.font = fontParts[0] + fontSize + 'px' + fontParts[1]; + } - _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; + function setLineHeight() { + if (!isNaN(opts.lineHeight)) { + lineHeight = fontSize * opts.lineHeight; + } else if (opts.lineHeight.toString().indexOf('px') !== -1) { + lineHeight = parseInt(opts.lineHeight); + } else if (opts.lineHeight.toString().indexOf('%') !== -1) { + lineHeight = (parseInt(opts.lineHeight) / 100) * fontSize; } - }, + } - _wrap: function() { - if (this.allowNewLine) { - var newLines = this.text.trim().split('\n'); - for (var i = 0,idx = 0; i < newLines.length - 1; i++) { + function wrap() { + if (opts.allowNewLine) { + var newLines = 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) + newLineIndexes.push(idx) } } - var words = this.text.trim().split(/\s+/); - this._checkLength(words); - this._breakText(words); + var words = text.trim().split(/\s+/); + checkLength(words); + breakText(words); - this.textBlockHeight = this.lines.length * this.lineHeight; - }, + textBlockHeight = lines.length * lineHeight; + } - _checkLength: function(words) { - var testString,tokenLen,sliced,leftover; + function checkLength(words) { + var testString, tokenLen, sliced, leftover; for (var i = 0; i < words.length; i++) { testString = ''; - tokenLen = this.context.measureText(words[i]).width; + tokenLen = 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++) { + for (var k = 0; (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); + sliced = words[i].slice(0, k); leftover = words[i].slice(k); - words.splice(i,1,sliced,leftover); + words.splice(i, 1, sliced, leftover); } } - }, + } - _breakText: function(words) { - for (var i = 0,j = 0; i < words.length; j++) { - this.lines[j] = ''; + function breakText(words) { + for (var i = 0, j = 0; i < words.length; j++) { + lines[j] = ''; - if (this.lineBreak === 'auto') { - while ((this.context.measureText(this.lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) { + if (opts.lineBreak === 'auto') { + while ((context.measureText(lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) { - this.lines[j] += words[i] + ' '; + lines[j] += words[i] + ' '; i++; - if (this.allowNewLine) { - for (var k = 0; k < this.newLineIndexes.length; k++) { - if (this.newLineIndexes[k] === i) { + if (opts.allowNewLine) { + for (var k = 0; k < newLineIndexes.length; k++) { + if (newLineIndexes[k] === i) { j++; - this.lines[j] = ''; + lines[j] = ''; break; } } } } - this.lines[j] = this.lines[j].trim(); + lines[j] = lines[j].trim(); } else { - this.lines[j] = words[i]; + 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; + function justify() { + var maxLen, longestLineIndex, tokenLen; + for (var i = 0; i < lines.length; i++) { + tokenLen = context.measureText(lines[i]).width; if (!maxLen || tokenLen > maxLen) { maxLen = tokenLen; @@ -184,18 +175,18 @@ } // fill lines with extra spaces - var numWords,spaceLength,numOfSpaces,num,filler; + var numWords, spaceLength, numOfSpaces, num, filler; var delimiter = '\u200A'; - for (i = 0; i < this.lines.length; i++) { + for (i = 0; i < lines.length; i++) { if (i === longestLineIndex) continue; - numWords = this.lines[i].trim().split(/\s+/).length; + numWords = lines[i].trim().split(/\s+/).length; if (numWords <= 1) continue; - this.lines[i] = this.lines[i].trim().split(/\s+/).join(delimiter); + lines[i] = lines[i].trim().split(/\s+/).join(delimiter); - spaceLength = this.context.measureText(delimiter).width; - numOfSpaces = (maxLen - this.context.measureText(this.lines[i]).width) / spaceLength; + spaceLength = context.measureText(delimiter).width; + numOfSpaces = (maxLen - context.measureText(lines[i]).width) / spaceLength; num = numOfSpaces / (numWords - 1); filler = ''; @@ -203,89 +194,89 @@ filler += delimiter; } - this.lines[i] = this.lines[i].trim().split(delimiter).join(filler); + lines[i] = 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]); + function drawText() { + for (var i = 0; i < lines.length; i++) { + setHorizontalAlign(lines[i]); - this.textPos.y = parseInt(this.textPos.y) + this.lineHeight; - this.context.fillText(this.lines[i],this.textPos.x,this.textPos.y); + textPos.y = parseInt(textPos.y) + lineHeight; + context.fillText(lines[i], textPos.x, textPos.y); - if (this.strokeText) { - this.context.strokeText(this.lines[i],this.textPos.x,this.textPos.y); + if (opts.strokeText) { + context.strokeText(lines[i], textPos.x, 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; + function setHorizontalAlign(line) { + if (opts.textAlign == 'center') { + textPos.x = (EL_WIDTH - context.measureText(line).width) / 2; + } else if (opts.textAlign == 'right') { + textPos.x = EL_WIDTH - context.measureText(line).width - opts.paddingX; } else { - this.textPos.x = this.paddingX; + textPos.x = opts.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; + function setVerticalAlign() { + if (opts.verticalAlign == 'middle') { + textPos.y = (EL_HEIGHT - textBlockHeight) / 2; + } else if (opts.verticalAlign == 'bottom') { + textPos.y = EL_HEIGHT - textBlockHeight - opts.paddingY; } else { - this.textPos.y = this.paddingY; + textPos.y = opts.paddingY; } - }, + } - _validate: function() { - if (!(this.canvas instanceof HTMLCanvasElement)) + function validate() { + if (!(canvas instanceof HTMLCanvasElement)) throw new TypeError('The first parameter must be an instance of HTMLCanvasElement.'); - if (typeof this.text !== 'string') + if (typeof text !== 'string') throw new TypeError('The second parameter must be a string.'); - if (isNaN(this.fontSize)) + if (isNaN(fontSize)) throw new TypeError('Cannot parse "font".'); - if (isNaN(this.lineHeight)) + if (isNaN(opts.lineHeight)) throw new TypeError('Cannot parse "lineHeight".'); - if (this.textAlign.toLocaleLowerCase() !== 'left' && this.textAlign.toLocaleLowerCase() !== 'center' && this.textAlign.toLocaleLowerCase() !== 'right') + if (opts.textAlign.toLocaleLowerCase() !== 'left' && opts.textAlign.toLocaleLowerCase() !== 'center' && opts.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') + if (opts.verticalAlign.toLocaleLowerCase() !== 'top' && opts.verticalAlign.toLocaleLowerCase() !== 'middle' && opts.verticalAlign.toLocaleLowerCase() !== 'bottom') throw new TypeError('Property "verticalAlign" must be set to either "top", "middle", or "bottom".'); - if (typeof this.justifyLines !== 'boolean') + if (typeof opts.justifyLines !== 'boolean') throw new TypeError('Property "justifyLines" must be set to a Boolean.'); - if (isNaN(this.paddingX)) + if (isNaN(opts.paddingX)) throw new TypeError('Property "paddingX" must be set to a Number.'); - if (isNaN(this.paddingY)) + if (isNaN(opts.paddingY)) throw new TypeError('Property "paddingY" must be set to a Number.'); - if (typeof this.fitParent !== 'boolean') + if (typeof opts.fitParent !== 'boolean') throw new TypeError('Property "fitParent" must be set to a Boolean.'); - if (this.lineBreak.toLocaleLowerCase() !== 'auto' && this.lineBreak.toLocaleLowerCase() !== 'word') + if (opts.lineBreak.toLocaleLowerCase() !== 'auto' && opts.lineBreak.toLocaleLowerCase() !== 'word') throw new TypeError('Property "lineBreak" must be set to either "auto" or "word".'); - if (typeof this.sizeToFill !== 'boolean') + if (typeof opts.sizeToFill !== 'boolean') throw new TypeError('Property "sizeToFill" must be set to a Boolean.'); - if (typeof this.strokeText !== 'boolean') + if (typeof opts.strokeText !== 'boolean') throw new TypeError('Property "strokeText" must be set to a Boolean.'); } - }; + } - if ('module' in root && 'exports' in module) { - module.exports = CanvasTextWrapper; - } else { - root.CanvasTextWrapper = CanvasTextWrapper; - } + if ('module' in root && 'exports' in module) { + module.exports = CanvasTextWrapper; + } else { + root.CanvasTextWrapper = CanvasTextWrapper; + } })(this); \ No newline at end of file diff --git a/CanvasTextWrapper.min.js b/CanvasTextWrapper.min.js index 319b0c3..bdc9924 100644 --- a/CanvasTextWrapper.min.js +++ b/CanvasTextWrapper.min.js @@ -2,6 +2,5 @@ * https://github.com/namniak/CanvasTextWrapper * Version: 0.4.0 * MIT License (http://www.opensource.org/licenses/mit-license.html) - * Copyright (c) 2014 Vadim Namniak */ -!function(a){"use strict";var b,c,d,e,f={font:"18px Arial, sans-serif",sizeToFill:!1,lineHeight:1,allowNewLine:!0,lineBreak:"auto",textAlign:"left",verticalAlign:"top",justifyLines:!1,paddingX:0,paddingY:0,fitParent:!1,strokeText:!1},g=function(a,h,i){if(!(this instanceof g))return new g(a,h,i);this.canvas=a,this.text=h,this.context=this.canvas.getContext("2d"),this.context.font=this.font,this.context.textBaseline="bottom";for(var j in f)f.hasOwnProperty(j)&&(this[j]=i&&i[j]?i[j]:f[j]);b=this.fitParent===!1?this.canvas.width:this.canvas.parentNode.clientWidth,c=this.fitParent===!1?this.canvas.height:this.canvas.parentNode.clientHeight,d=b-2*this.paddingX,e=c-2*this.paddingY,this._init()};g.prototype={_init:function(){this.fontSize=this.font.match(/\d+(px|em|\%)/g)?+this.font.match(/\d+(px|em|\%)/g)[0].match(/\d+/g):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 a=this.text.trim().split(/\s+/).length,b=0;do this._setFont(++b),this.lineHeight=this.fontSize,this._wrap();while(this.textBlockHeightd){for(var h=0;this.context.measureText(b+a[g][h]).width<=d&&ha)&&(a=c,b=d);var e,f,g,h,i,j=" ";for(d=0;d=e))){this.lines[d]=this.lines[d].trim().split(/\s+/).join(j),f=this.context.measureText(j).width,g=(a-this.context.measureText(this.lines[d]).width)/f,h=g/(e-1),i="";for(var k=0;h>k;k++)i+=j;this.lines[d]=this.lines[d].trim().split(j).join(i)}},_drawText:function(){for(var a=0;at&&u.join(" ").split(/\s+/).length==a);f(--d),x=d}else h();c.justifyLines&&"auto"===c.lineBreak&&k(),n(),l()}function f(a){var b=c.sizeToFill?r.font.split(/\b\d+px\b/i):c.font.split(/\b\d+px\b/i);r.font=b[0]+a+"px"+b[1]}function g(){isNaN(c.lineHeight)?-1!==c.lineHeight.toString().indexOf("px")?x=parseInt(c.lineHeight):-1!==c.lineHeight.toString().indexOf("%")&&(x=parseInt(c.lineHeight)/100*s):x=s*c.lineHeight}function h(){if(c.allowNewLine)for(var a=b.trim().split("\n"),d=0,e=0;dA){for(var g=0;r.measureText(b+a[f][g]).width<=A&&ga)&&(a=c,b=d);var e,f,g,h,i,j=" ";for(d=0;d=e))){u[d]=u[d].trim().split(/\s+/).join(j),f=r.measureText(j).width,g=(a-r.measureText(u[d]).width)/f,h=g/(e-1),i="";for(var k=0;h>k;k++)i+=j;u[d]=u[d].trim().split(j).join(i)}}function l(){for(var a=0;a