7 Commits

Author SHA1 Message Date
Vadim Namniak ffcaf3425c Update README.md 2015-01-25 15:27:49 -05:00
Vadim Namniak 365bd4e24c Update README.md 2015-01-25 15:27:19 -05:00
Vadim Namniak 96001e64df Update README.md 2015-01-25 15:26:14 -05:00
Vadim Namniak 49fea5c2bb Update README.md 2015-01-25 15:23:53 -05:00
Vadim Namniak 020d1dee52 Update README.md 2015-01-25 15:22:28 -05:00
Vadim Namniak c75fae5ea0 Update README.md 2015-01-25 15:18:39 -05:00
Vadim Namniak 9b2c460a69 v0.3.0 justifyLines, lineHeight and allowNewLine 2015-01-25 14:53:51 -05:00
6 changed files with 306 additions and 232 deletions
+2 -1
View File
@@ -1 +1,2 @@
examples examples/
.idea/
+247 -170
View File
@@ -1,206 +1,283 @@
/*! CanvasTextWrapper (https://github.com/namniak/CanvasTextWrapper) /*! CanvasTextWrapper
* Version: 0.2.3 * https://github.com/namniak/CanvasTextWrapper
* * Version: 0.3.0
* MIT License (http://www.opensource.org/licenses/mit-license.html) * MIT License (http://www.opensource.org/licenses/mit-license.html)
* Copyright (c) 2014 Vadim Namniak * Copyright (c) 2014 Vadim Namniak
*/ */
(function() { (function() {
'use strict'; 'use strict';
var defaultOptions = { var EL_WIDTH,EL_HEIGHT,MAX_TXT_WIDTH,MAX_TXT_HEIGHT;
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 (given font size is ignored)
strokeText: false // text is stroked according to context configuration.
};
window.CanvasTextWrapper = function(canvas, text, opts) { 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
};
if (!(this instanceof CanvasTextWrapper)) { var CanvasTextWrapper = function(canvas,text,options) {
throw new TypeError('CanvasTextWrapper constructor failed. Use "new" keyword when instantiating.'); if (!(this instanceof CanvasTextWrapper)) {
} return new CanvasTextWrapper(canvas,text,options);
}
this.canvas = canvas; this.canvas = canvas;
this.text = text; this.text = text;
this.context = this.canvas.getContext('2d');
this.context.font = this.font;
this.context.textBaseline = 'bottom';
// set options to specified or default values for (var property in defaults) {
for (var property in defaultOptions) { if (defaults.hasOwnProperty(property)) {
this[property] = (opts && opts[property]) ? opts[property] : defaultOptions[property]; this[property] = (options && options[property]) ? options[property] : defaults[property];
} }
}
// extract font size EL_WIDTH = (this.fitParent === false) ? this.canvas.width : this.canvas.parentNode.clientWidth;
this.lineHeight = parseInt(this.font.replace(/^\D+/g, ''), 10) || 18; 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);
// validate all set properties this._init();
this.validate(); };
// basic context settings CanvasTextWrapper.prototype = {
this.context = this.canvas.getContext('2d'); _init: function() {
this.context.font = this.font; this.fontSize = parseInt(this.font.replace(/^\D+/g,''),10) || 18;
this.context.textBaseline = 'bottom'; this.textBlockHeight = 0;
this.lines = [];
this.newLineIndexes = [];
this.textPos = {x: 0,y: 0};
this.drawText(); this._setFont(this.fontSize);
}; this._setLineHeight();
this._validate();
this._render();
},
CanvasTextWrapper.prototype = { _render: function() {
if (this.sizeToFill) {
var numWords = this.text.trim().split(/\s+/).length;
var fontSize = 0;
drawText: function() { do {
var elementWidth = (this.fitParent === false) ? this.canvas.width : this.canvas.parentNode.clientWidth; this._setFont(++fontSize);
var textPos = { this.lineHeight = this.fontSize;
x: 0, this._wrap();
y: 0 } while (this.textBlockHeight < MAX_TXT_HEIGHT && (this.lines.join(' ').split(/\s+/).length == numWords));
};
if (this.sizeToFill) { this._setFont(--fontSize);
// starting at 1px increase font size by 1px until text block exceeds the height of its padded container or until words break this.lineHeight = this.fontSize;
var elementHeight = ((this.fitParent === false) ? this.canvas.height : this.canvas.parentNode.clientHeight) - (this.paddingX * 2); } else {
var numWords = this.text.trim().split(/\s+/).length; this._wrap();
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 if (this.justifyLines && this.lineBreak === 'auto') {
this.setFontSize(--fontSize); this._justify();
} }
var lines = this.getWrappedText(elementWidth); this._setAlignY();
var textBlockHeight = lines.length * this.lineHeight; this._drawText();
},
// set vertical align for the whole text block _setFont: function(fontSize) {
this.setTextVerticalAlign(textPos, textBlockHeight); 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;
},
for (var i = 0; i < lines.length; i++) { _setLineHeight: function() {
this.setTextHorizontalAlign(this.context, textPos, elementWidth, lines[i]); 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;
}
},
textPos.y = parseInt(textPos.y) + parseInt(this.lineHeight); _wrap: function() {
this.context.fillText(lines[i], textPos.x, textPos.y); if (this.allowNewLine) {
if (this.strokeText) { var newLines = this.text.trim().split('\n');
this.context.strokeText(lines[i], textPos.x, textPos.y); for (var i = 0,idx = 0; i < newLines.length - 1; i++) {
} idx += newLines[i].trim().split(/\s+/).length;
} this.newLineIndexes.push(idx)
}, }
}
setFontSize: function(size) { var words = this.text.trim().split(/\s+/);
var fontParts = this.context.font.split(/\b\d+px\b/i); this._checkLength(words);
this.context.font = fontParts[0] + size + 'px' + fontParts[1]; this._breakText(words);
this.lineHeight = size;
},
getWrappedText: function(elementWidth) { this.textBlockHeight = this.lines.length * this.lineHeight;
var maxTextLength = elementWidth - (this.paddingX * 2); },
var words = this.text.trim().split(/\s+/); _checkLength: function(words) {
var lines = []; var testString,tokenLen,sliced,leftover;
this.checkWordsLength(this.context, words, maxTextLength); for (var i = 0; i < words.length; i++) {
this.breakTextIntoLines(this.context, lines, words, maxTextLength); testString = '';
tokenLen = this.context.measureText(words[i]).width;
return lines; 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];
}
checkWordsLength: function(context, words, maxTextLength) { sliced = words[i].slice(0,k);
for (var i = 0; i < words.length; i++) { leftover = words[i].slice(k);
var testString = ''; words.splice(i,1,sliced,leftover);
var tokenLen = context.measureText(words[i]).width; }
}
},
// check if a word exceeds the element's width _breakText: function(words) {
if (tokenLen > maxTextLength) { for (var i = 0,j = 0; i < words.length; j++) {
for (var k = 0; (context.measureText(testString + words[i][k]).width <= maxTextLength) && (k < words[i].length); k++) { this.lines[j] = '';
testString += words[i][k];
}
// break the word because it's too long if (this.lineBreak === 'auto') {
var sliced = words[i].slice(0, k); while ((this.context.measureText(this.lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) {
var leftover = words[i].slice(k);
words.splice(i, 1, sliced, leftover);
}
}
},
breakTextIntoLines: function(context, lines, words, maxTextLength) { this.lines[j] += words[i] + ' ';
for (var i = 0, j = 0; i < words.length; j++) { i++;
lines[j] = '';
if (this.lineBreak === 'auto') { if (this.allowNewLine) {
// put as many full words in a line as can fit element for (var k = 0; k < this.newLineIndexes.length; k++) {
while ((context.measureText(lines[j] + words[i]).width <= maxTextLength) && (i < words.length)) { if (this.newLineIndexes[k] === i) {
lines[j] += words[i] + ' '; j++;
i++; this.lines[j] = '';
} break;
lines[j] = lines[j].trim(); }
} else if (this.lineBreak === 'word') { }
// put each next word in a new line }
lines[j] = words[i]; }
i++; this.lines[j] = this.lines[j].trim();
} } else {
} this.lines[j] = words[i];
}, i++;
}
}
},
setTextHorizontalAlign: function(context, textPos, elementWidth, line) { _justify: function() {
if (this.textAlign === 'center') { var maxLen,longestLineIndex,tokenLen;
textPos.x = (elementWidth - context.measureText(line).width) / 2; for (var i = 0; i < this.lines.length; i++) {
} else if (this.textAlign === 'right') { tokenLen = this.context.measureText(this.lines[i]).width;
textPos.x = elementWidth - context.measureText(line).width - this.paddingX;
} else {
textPos.x = this.paddingX;
}
},
setTextVerticalAlign: function(textPos, textBlockHeight) { if (!maxLen || tokenLen > maxLen) {
var elementHeight = (this.fitParent === false) ? this.canvas.height : this.canvas.parentNode.clientHeight; maxLen = tokenLen;
longestLineIndex = i;
}
}
if (this.verticalAlign === 'middle') { // fill lines with extra spaces
textPos.y = (elementHeight - textBlockHeight) / 2; var numWords,spaceLength,numOfSpaces,num,filler;
} else if (this.verticalAlign === 'bottom') { var delimiter = '\u200A';
textPos.y = elementHeight - textBlockHeight - this.paddingY; for (i = 0; i < this.lines.length; i++) {
} else { if (i === longestLineIndex) continue;
textPos.y = this.paddingY;
}
},
validate: function() { numWords = this.lines[i].trim().split(/\s+/).length;
if (!(this.canvas instanceof HTMLCanvasElement)) { if (numWords <= 1) continue;
throw new TypeError('From CanvasTextWrapper(): Element passed as the first parameter is not an instance of HTMLCanvasElement.');
} this.lines[i] = this.lines[i].trim().split(/\s+/).join(delimiter);
if (typeof this.text !== 'string') {
throw new TypeError('From CanvasTextWrapper(): The second, dedicated for the text, parameter must be a string.'); spaceLength = this.context.measureText(delimiter).width;
} numOfSpaces = (maxLen - this.context.measureText(this.lines[i]).width) / spaceLength;
if (isNaN(this.lineHeight)) { num = numOfSpaces / (numWords - 1);
throw new TypeError('From CanvasTextWrapper(): Cannot parse font size as an Integer. Check "font" property\'s value.');
} filler = '';
if (this.textAlign !== 'left' && this.textAlign !== 'center' && this.textAlign !== 'right') { for (var j = 0; j < num; j++) {
throw new TypeError('From CanvasTextWrapper(): Unsupported horizontal align value is used. Property "textAlign" can only be set to "left", "center", or "right".'); filler += delimiter;
} }
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".'); this.lines[i] = this.lines[i].trim().split(delimiter).join(filler);
} //console.log('numWords:', numWords, 'numOfSpaces:', numOfSpaces, 'num:', num);
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)) { _drawText: function() {
throw new TypeError('From CanvasTextWrapper(): Unsupported vertical padding value is used. Property "paddingY" must be set to a number.'); for (var i = 0; i < this.lines.length; i++) {
} this._setAlignX(this.lines[i]);
if (typeof this.fitParent !== 'boolean') {
throw new TypeError('From CanvasTextWrapper(): Property "fitParent" must be set to a Boolean.'); this.textPos.y = parseInt(this.textPos.y) + this.lineHeight;
} this.context.fillText(this.lines[i],this.textPos.x,this.textPos.y);
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 (this.strokeText) {
} this.context.strokeText(this.lines[i],this.textPos.x,this.textPos.y);
if (typeof this.sizeToFill !== 'boolean') { }
throw new TypeError('From CanvasTextWrapper(): Property "sizeToFill" must be set to a Boolean.'); }
} },
if (typeof this.strokeText !== 'boolean') {
throw new TypeError('From CanvasTextWrapper(): Property "strokeText" must be set to a Boolean.'); _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;
})();
+4 -4
View File
File diff suppressed because one or more lines are too long
+34 -38
View File
@@ -6,55 +6,51 @@ CanvasTextWrapper
new CanvasTextWrapper(HTMLCanvasElement, String [, options]); new CanvasTextWrapper(HTMLCanvasElement, String [, options]);
``` ```
```options``` - is a JavaScript object with the following available properties and values: ```options``` - is an object with the following available properties and values:
- ```font: String``` - text style that includes font size (in px), weight and family, similarly to CSS font shorthand property - ```font:``` (String) - text style that includes font size in px, font weight, font family, etc. Similar to CSS font shorthand property
- ```textAlign: "left" | "center" | "right"``` - horizontal alignment for each line - ```lineHeight:``` (String or Number) - Number means n times font size where 1 is equivalent to '100%'. Also the property can be set in "%" or "px" using String
- ```verticalAlign: "top" | "middle" | "bottom"``` - vertical alignment for the whole text block - ```textAlign: "left" | "center" | "right"``` - horizontal alignment of each line
- ```paddingX: Number``` - horizontal padding in pixels set equally on both, left and right sides of the element - ```verticalAlign: "top" | "middle" | "bottom"``` - vertical alignment of the whole text block
- ```paddingY: Number``` - vertical padding in pixels set equally on both, top and bottom sides of the element - ```paddingX:``` (Number) - horizontal padding (in px) set equally on both, left and right sides
- ```fitParent: Boolean``` - parameter that controls which element to fit where ```true``` means fit canvas parent's width instead of canvas own width - ```paddingY:``` (Number) - vertical padding (in px) set equally on both, top and bottom sides
- ```lineBreak: "auto" | "word"``` - text split rule. When using ```"auto"```, text fills the element's width, going to a new line on a whole word when there's no more room. If ```"word"``` is set as value, each next word will be placed on a new line. - ```fitParent:``` (Boolean) - if enabled, text will fit canvas container's width instead of canvas own width
- ```sizeToFill: Boolean``` - ignore given font size and resize text to fill its padded container - ```lineBreak: "auto" | "word"``` - text split rule. When using ```"auto"```, text goes to a next line on a whole word when there's no more room. If ```"word"``` is set as value, each next word will be placed on a new line.
- ```strokeText: Boolean``` - add text outline based on context configuration (make sure it doesn't contradict with other context settings such as globalCompositeOperation, etc) - ```sizeToFill:``` (Boolean) - ignore given font size and line height and resize text to fill its padded container
- ```strokeText:``` (Boolean) - outline text based on context configuration
- ```justifyLines:``` (Boolean) - if enabled, all lines match the same width with flexed spaces between words (one-word lines are ignored).
- ```allowNewLine:``` (Boolean) if enabled, the text breaks on every new line character "\n" otherwise it'll be considered as a space
NOTE: if a single word is too long to fit the width with specified font size, it will be broken into as many lines as required on any letter of the word unless ```sizeToFill``` option is used. NOTE: if a single word is too long to fit the width with specified font size, it will break on any letter unless ```sizeToFill``` option is enabled.
##Defaults ##Defaults
The default options object which values will be used if a property is not specified or no object is passed:
``` ```
{ {
font: "18px Arial, sans-serif", font: "18px Arial, sans-serif",
textAlign: "left", lineHeight: 1,
verticalAlign: "top", textAlign: "left",
paddingX: 0, verticalAlign: "top",
paddingY: 0, paddingX: 0,
fitParent: false, paddingY: 0,
lineBreak: "auto", fitParent: false,
sizeToFill: false, lineBreak: "auto",
strokeText: false strokeText: false
} sizeToFill: false,
allowNewLine: true,
justifyLines: false
}
``` ```
##Usage ##Usage
Use standard canvas text drawing сщташпгкфешщты such as "fillStyle" and "globalCompositeOperation" when needed before using CanvasTextWrapper like so: Configure context settings properties such as "fillStyle", "lineWidth" or "strokeStyle" before using CanvasTextWrapper like so:
``` ```
var canvas = document.createElement('canvas'); var canvas = document.getElementById("#canvasText");
canvas.width = 300; canvas.width = 200;
canvas.height = 250; canvas.height = 200;
context = canvas.getContext("2d"); context = canvas.getContext("2d");
context.fillStyle = "rgb(255, 255, 255)"; context.lineWidth = 2;
context.fillRect(0, 0, canvas.width, canvas.height); context.strokeStyle = "#ff0000";
CanvasTextWrapper(canvas,"Hello"); //default options will apply
new CanvasTextWrapper(canvas, "Hi there", {
font: "normal 40px Open Sans, sans-serif",
textAlign: "center",
verticalAlign: "bottom",
paddingY: 10,
lineBreak: "word",
});
``` ```
##Examples ##Examples
+16 -16
View File
@@ -1,18 +1,18 @@
{ {
"name": "canvas-text-wrapper", "name": "canvas-text-wrapper",
"version": "0.2.3", "version": "0.3.0",
"ignore": [ "ignore": [
"**/.*", "**/.*",
"**/*.log", "**/*.log",
"**/*.json", "**/*.json",
"Gruntfile.js", "Gruntfile.js",
"examples", "examples",
"node_modules", "node_modules",
"README.md" "README.md"
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/namniak/CanvasTextWrapper.git" "url": "git://github.com/namniak/CanvasTextWrapper.git"
}, },
"homepage": "http://namniak.github.io/CanvasTextWrapper/" "homepage": "http://namniak.github.io/CanvasTextWrapper/"
} }
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "canvas-text-wrapper", "name": "canvas-text-wrapper",
"description": "JavaScript canvas text wrapper that automatically splits a string into lines on specified rule with optional alignments and padding.", "description": "Pure JavaScript canvas text wrapper that automatically splits a string into lines on specified rule with alignment and padding.",
"version": "0.2.3", "version": "0.3.0",
"license": "MIT", "license": "MIT",
"main": "CanvasTextWrapper.min.js", "main": "CanvasTextWrapper.min.js",
"homepage": "http://namniak.github.io/CanvasTextWrapper/", "homepage": "http://namniak.github.io/CanvasTextWrapper/",
@@ -13,4 +13,4 @@
"bugs": { "bugs": {
"url": "https://github.com/namniak/CanvasTextWrapper/issues" "url": "https://github.com/namniak/CanvasTextWrapper/issues"
} }
} }