Repository: namniak/canvas-text-wrapper
Branch: master
Commit: 268a8620ab05
Files: 10
Total size: 19.5 KB
Directory structure:
gitextract_xkcmgvhz/
├── .gitignore
├── .npmignore
├── Gruntfile.js
├── LICENSE.md
├── README.md
├── bower.json
├── canvas-text-wrapper.js
├── index.d.ts
├── package.json
└── test/
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea/
node_modules/
================================================
FILE: .npmignore
================================================
node_modules
examples
bower.json
================================================
FILE: Gruntfile.js
================================================
module.exports = function (grunt) {
var saveLicense = require('uglify-save-license');
// project configuration
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
my_target: {
options: {
preserveComments: saveLicense
},
src: ['canvas-text-wrapper.js'],
dest: 'canvas-text-wrapper.min.js'
}
}
});
// load plugins
grunt.loadNpmTasks('uglify-save-license');
grunt.loadNpmTasks('grunt-contrib-uglify');
// default tasks.
grunt.registerTask('default', [
'uglify'
]);
};
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) 2016 Vadim Namniak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# canvas-text-wrapper
## Syntax
```javascript
CanvasTextWrapper(HTMLCanvasElement, String [, options]);
```
## Options
| Option | Value | Description |
| ------------- | ------------- | ------------- |
| **font** | *String* | Text style that includes font size (in px), font weight, font family, etc. |
| **lineHeight** | *String* or *Number* | Number - 'n' times font size where 1 is equivalent to '100%'. Also the property can be set in '%' or 'px'. |
| **textAlign** | `"left"` `"center"` `"right"` | Horizontal alignment of each line. |
| **verticalAlign** | `"top"` `"middle"` `"bottom"` | Vertical alignment of the whole text block. |
| **paddingX** | *Number* | Horizontal padding (in px) that is equally set on left and right sides. |
| **paddingY** | *Number* | Vertical padding (in px) that is equally set on top and bottoms. |
| **fitParent** | *Boolean* | Fit canvas' container size instead of its own size. |
| **lineBreak** | `"auto"` `"word"` | `"auto"` - text goes to the next line on a whole word when there's no room `"word"` - each next word is placed on a new line |
| **sizeToFill** | *Boolean* | Ignore given font size and line height and resize text to fill its padded container. |
| **maxFontSizeToFill** | *Number* | If above option is `true` text won't be bigger than set. |
| **strokeText** | *Boolean* | Allow text outline based on canvas context configuration. |
| **justifyLines** | *Boolean* | All lines will try to match the same width with flexed spaces between the words. |
| **allowNewLine** | *Boolean* | Text breaks on a new line character "\n". Supports multiple consecutive new lines. |
| **renderHDPI** | *Boolean* | Text is rendered based on device pixel ratio. |
| **textDecoration** | `"none"` `"underline"` | Text is underlined according to `context.strokeStyle` and `context.lineWidth` |
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.
## Default options
```javascript
{
font: '18px Arial, sans-serif',
lineHeight: 1,
textAlign: 'left',
verticalAlign: 'top',
paddingX: 0,
paddingY: 0,
fitParent: false,
lineBreak: 'auto',
strokeText: false
sizeToFill: false,
maxFontSizeToFill: false,
allowNewLine: true,
justifyLines: false,
renderHDPI: true,
textDecoration: 'none'
}
```
## Usage
Configure context properties such as ```fillStyle```, ```lineWidth```, ```strokeStyle``` etc. before passing it to CanvasTextWrapper like so:
```javascript
var CanvasTextWrapper = require('canvas-text-wrapper').CanvasTextWrapper;
var canvas = document.getElementById('#canvasText');
canvas.width = 200;
canvas.height = 200;
context = canvas.getContext('2d');
context.lineWidth = 2;
context.strokeStyle = '#ff0000';
CanvasTextWrapper(canvas, 'Hello', {strokeText: true});
```
## Test
Run ```npm t```
NOTE: Test requires [beefy](http://didact.us/beefy/) to be installed globally
## Examples
[see here](http://namniak.github.io/canvas-text-wrapper/)
## Install
```sh
npm i canvas-text-wrapper --save
bower install canvas-text-wrapper
```
================================================
FILE: bower.json
================================================
{
"name": "canvas-text-wrapper",
"version": "0.9.2",
"ignore": [
"**/.*",
"**/*.log",
"**/*.json",
"Gruntfile.js",
"examples",
"node_modules",
"README.md"
],
"repository": {
"type": "git",
"url": "git://github.com/namniak/canvas-text-wrapper.git"
},
"homepage": "http://namniak.github.io/canvas-text-wrapper/"
}
================================================
FILE: canvas-text-wrapper.js
================================================
(function (root) {
function CanvasTextWrapper(canvas, text, options) {
'use strict';
var defaults = {
font: '18px Arial, sans-serif',
sizeToFill: false,
maxFontSizeToFill: false,
lineHeight: 1,
allowNewLine: true,
lineBreak: 'auto',
textAlign: 'left',
verticalAlign: 'top',
justifyLines: false,
paddingX: 0,
paddingY: 0,
fitParent: false,
strokeText: false,
renderHDPI: true,
textDecoration: 'none'
};
var opts = {};
for (var key in defaults) {
opts[key] = options.hasOwnProperty(key) ? options[key] : defaults[key];
}
var context = canvas.getContext('2d');
context.font = opts.font;
context.textBaseline = 'bottom';
var scale = 1;
var devicePixelRatio = (typeof global !== 'undefined') ? global.devicePixelRatio : root.devicePixelRatio;
if (opts.renderHDPI && devicePixelRatio > 1) {
var tempCtx = {};
// store context settings in a temp object before scaling otherwise they will be lost
for (var key in context) {
tempCtx[key] = context[key];
}
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
scale = devicePixelRatio;
canvas.width = canvasWidth * scale;
canvas.height = canvasHeight * scale;
canvas.style.width = canvasWidth * scale * 0.5 + 'px';
canvas.style.height = canvasHeight * scale * 0.5 + 'px';
// restore context settings
for (var key in tempCtx) {
try {
context[key] = tempCtx[key];
} catch (e) {
}
}
context.scale(scale, scale);
}
var EL_WIDTH = (!opts.fitParent ? canvas.width : canvas.parentNode.clientWidth) / scale;
var EL_HEIGHT = (!opts.fitParent ? canvas.height : canvas.parentNode.clientHeight) / scale;
var MAX_TXT_WIDTH = EL_WIDTH - (opts.paddingX * 2);
var MAX_TXT_HEIGHT = EL_HEIGHT - (opts.paddingY * 2);
var fontSize = opts.font.match(/\d+(px|em|%)/g) ? +opts.font.match(/\d+(px|em|%)/g)[0].match(/\d+/g) : 18;
var textBlockHeight = 0;
var lines = [];
var newLineIndexes = [];
var textPos = {x: 0, y: 0};
var lineHeight = 0;
var fontParts;
var multiNewLineDelimiter = '\u200B';
text = handleMultipleNewline(text);
setFont(fontSize);
setLineHeight();
validate();
render();
function handleMultipleNewline (text) {
do {
text = text.replace(/\n\n/g, '\n' + multiNewLineDelimiter + '\n');
} while (text.indexOf('\n\n') > -1);
return text;
}
function setFont(fontSize) {
if (!fontParts) 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];
}
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;
}
}
function render() {
if (opts.sizeToFill) {
var wordsCount = text.trim().split(/\s+/).length;
var newFontSize = 0;
var fontSizeHasLimit = opts.maxFontSizeToFill !== false;
do {
if (fontSizeHasLimit) {
if (++newFontSize <= opts.maxFontSizeToFill) {
adjustFontSize(newFontSize);
} else {
break;
}
} else {
adjustFontSize(++newFontSize);
}
} while (textBlockHeight < MAX_TXT_HEIGHT && (lines.join(' ').split(/\s+/).length == wordsCount));
adjustFontSize(--newFontSize);
} else {
wrap();
}
if (opts.justifyLines && opts.lineBreak === 'auto') {
justify();
}
setVertAlign();
setHorizAlign();
drawText();
}
function adjustFontSize(size) {
setFont(size);
lineHeight = size;
wrap();
}
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;
newLineIndexes.push(idx)
}
}
var words = text.trim().split(/\s+/);
checkLength(words);
breakText(words);
textBlockHeight = lines.length * lineHeight;
}
function checkLength(words) {
var testString, tokenLen, sliced, leftover;
words.forEach(function (word, index) {
testString = '';
tokenLen = context.measureText(word).width;
if (tokenLen > MAX_TXT_WIDTH) {
for (var k = 0; (context.measureText(testString + word[k]).width <= MAX_TXT_WIDTH) && (k < word.length); k++) {
testString += word[k];
}
sliced = word.slice(0, k);
leftover = word.slice(k);
words.splice(index, 1, sliced, leftover);
}
});
}
function breakText(words) {
lines = [];
for (var i = 0, j = 0; i < words.length; j++) {
lines[j] = '';
if (opts.lineBreak === 'auto') {
if (context.measureText(lines[j] + words[i]).width > MAX_TXT_WIDTH) {
break;
} else {
while ((context.measureText(lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) {
lines[j] += words[i] + ' ';
i++;
if (opts.allowNewLine) {
for (var k = 0; k < newLineIndexes.length; k++) {
if (newLineIndexes[k] === i) {
j++;
lines[j] = '';
break;
}
}
}
}
}
lines[j] = lines[j].trim();
} else {
lines[j] = words[i];
i++;
}
}
}
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;
longestLineIndex = i;
}
}
// fill lines with extra spaces
var numWords, spaceLength, numOfSpaces, num, filler;
var delimiter = '\u200A';
for (i = 0; i < lines.length; i++) {
if (i === longestLineIndex) continue;
numWords = lines[i].trim().split(/\s+/).length;
if (numWords <= 1) continue;
lines[i] = lines[i].trim().split(/\s+/).join(delimiter);
spaceLength = context.measureText(delimiter).width;
numOfSpaces = (maxLen - context.measureText(lines[i]).width) / spaceLength;
num = numOfSpaces / (numWords - 1);
filler = '';
for (var j = 0; j < num; j++) {
filler += delimiter;
}
lines[i] = lines[i].trim().split(delimiter).join(filler);
}
}
function underline(text, x, y) {
var width = context.measureText(text).width;
switch (context.textAlign) {
case 'center':
x -= (width / 2);
break;
case 'right':
x -= width;
break;
}
context.beginPath();
context.moveTo(x, y);
context.lineTo(x + width, y);
context.stroke();
}
function drawText() {
var skipLineOnMatch = multiNewLineDelimiter + ' ';
for (var i = 0; i < lines.length; i++) {
textPos.y = parseInt(textPos.y) + lineHeight;
if (lines[i] !== skipLineOnMatch) {
context.fillText(lines[i], textPos.x, textPos.y);
if (opts.strokeText) {
context.strokeText(lines[i], textPos.x, textPos.y);
}
if (opts.textDecoration.toLocaleLowerCase() === 'underline') {
underline(lines[i], textPos.x, textPos.y);
}
}
}
}
function setHorizAlign() {
context.textAlign = opts.textAlign;
if (opts.textAlign == 'center') {
textPos.x = EL_WIDTH / 2;
} else if (opts.textAlign == 'right') {
textPos.x = EL_WIDTH - opts.paddingX;
} else {
textPos.x = opts.paddingX;
}
}
function setVertAlign() {
if (opts.verticalAlign == 'middle') {
textPos.y = (EL_HEIGHT - textBlockHeight) / 2;
} else if (opts.verticalAlign == 'bottom') {
textPos.y = EL_HEIGHT - textBlockHeight - opts.paddingY;
} else {
textPos.y = opts.paddingY;
}
}
function validate() {
if (typeof text !== 'string')
throw new TypeError('The second parameter must be a String.');
if (isNaN(fontSize))
throw new TypeError('Cannot parse "font".');
if (isNaN(lineHeight))
throw new TypeError('Cannot parse "lineHeight".');
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 (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 opts.justifyLines !== 'boolean')
throw new TypeError('Property "justifyLines" must be a Boolean.');
if (isNaN(opts.paddingX))
throw new TypeError('Property "paddingX" must be a Number.');
if (isNaN(opts.paddingY))
throw new TypeError('Property "paddingY" must be a Number.');
if (typeof opts.fitParent !== 'boolean')
throw new TypeError('Property "fitParent" must be a Boolean.');
if (opts.lineBreak.toLocaleLowerCase() !== 'auto' && opts.lineBreak.toLocaleLowerCase() !== 'word')
throw new TypeError('Property "lineBreak" must be set to either "auto" or "word".');
if (typeof opts.sizeToFill !== 'boolean')
throw new TypeError('Property "sizeToFill" must be a Boolean.');
if (typeof opts.strokeText !== 'boolean')
throw new TypeError('Property "strokeText" must be a Boolean.');
if (typeof opts.renderHDPI !== 'boolean')
throw new TypeError('Property "renderHDPI" must be a Boolean.');
if (opts.textDecoration.toLocaleLowerCase() !== 'none' && opts.textDecoration.toLocaleLowerCase() !== 'underline')
throw new TypeError('Property "textDecoration" must be set to either "none" or "underline".');
}
return(lines);
}
if ('module' in root && 'exports' in module) {
module.exports = CanvasTextWrapper;
} else {
root.CanvasTextWrapper = CanvasTextWrapper;
}
})(this);
================================================
FILE: index.d.ts
================================================
export function CanvasTextWrapper(element: HTMLCanvasElement, text: string, options?: CanvasTextWrapperOptions): void;
export interface CanvasTextWrapperOptions {
/**
* Text style that includes font size (in px), font weight, font family, etc.
*/
font?: string,
/**
* Number - 'n' times font size where 1 is equivalent to '100%'. Also the property can be set in '%' or 'px'.
*/
lineHeight?: string | number,
/**
* Horizontal alignment of each line.
*/
textAlign?: "left" | "center" | "right",
/**
* Vertical alignment of the whole text block.
*/
verticalAlign?: "top" | "middle" | "bottom",
/**
* Horizontal padding (in px) that is equally set on left and right sides.
*/
paddingX?: number,
/**
* Vertical padding (in px) that is equally set on top and bottoms.
*/
paddingY?: number,
/**
* Fit canvas' container size instead of its own size.
*/
fitParent?: boolean,
/**
* "auto" - text goes to the next line on a whole word when there's no room
* "word" - each next word is placed on a new line
*/
lineBreak?: "auto" | "word",
/**
* Ignore given font size and line height and resize text to fill its padded container.
*/
sizeToFill?: boolean,
/**
* If above option is true text won't be bigger than set.
*/
maxFontSizeToFill?: number,
/**
* Allow text outline based on canvas context configuration.
*/
strokeText?: boolean,
/**
* All lines will try to match the same width with flexed spaces between the words.
*/
justifyLines?: boolean,
/**
* Text breaks on a new line character "\n". Supports multiple consecutive new lines.
*/
allowNewLine?: boolean,
/**
* Text is rendered based on device pixel ratio.
*/
renderHDPI?: boolean,
/**
* Text is underlined according to context.strokeStyle and context.lineWidth
*/
textDecoration?: "none" | "underline"
}
================================================
FILE: package.json
================================================
{
"name": "canvas-text-wrapper",
"description": "Split canvas text into lines on specified rule with optional alignment, padding, and more. Supports HDPI screens.",
"version": "0.10.2",
"license": "MIT",
"main": "canvas-text-wrapper.js",
"keywords": [
"canvas",
"canvas-text",
"text",
"split",
"wrap",
"hdpi-canvas",
"text-split"
],
"homepage": "http://namniak.github.io/canvas-text-wrapper/",
"repository": {
"type": "git",
"url": "https://github.com/namniak/canvas-text-wrapper"
},
"author": "Vadim Namniak <vnamnyak@gmail.com> (https://github.com/namniak)",
"bugs": {
"url": "https://github.com/namniak/canvas-text-wrapper/issues"
},
"scripts": {
"test": "beefy test/test.js --live --open"
},
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-uglify": "^0.9.1",
"uglify-save-license": "^0.4.1"
}
}
================================================
FILE: test/test.js
================================================
var CanvasTextWrapper = require('../canvas-text-wrapper.js').CanvasTextWrapper;
var body = document.getElementsByTagName('body')[0];
body.style.margin = 0;
var canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 600;
canvas.style.position = 'absolute';
canvas.style.left = '50%';
canvas.style.top = '50%';
canvas.style.border = '1px solid #212121';
canvas.style.transform = 'translateX(-50%) translateY(-50%)';
body.appendChild(canvas);
var ctx = canvas.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop('0.2', 'magenta');
gradient.addColorStop('0.5', 'blue');
gradient.addColorStop('0.7', 'purple');
ctx.fillStyle = gradient;
var opts = {
sizeToFill: true,
textAlign: 'center',
verticalAlign: 'middle',
paddingX: 20
};
CanvasTextWrapper(canvas, 'What an awesome library!', opts);
gitextract_xkcmgvhz/
├── .gitignore
├── .npmignore
├── Gruntfile.js
├── LICENSE.md
├── README.md
├── bower.json
├── canvas-text-wrapper.js
├── index.d.ts
├── package.json
└── test/
└── test.js
SYMBOL INDEX (2 symbols across 2 files)
FILE: canvas-text-wrapper.js
function CanvasTextWrapper (line 3) | function CanvasTextWrapper(canvas, text, options) {
FILE: index.d.ts
type CanvasTextWrapperOptions (line 4) | interface CanvasTextWrapperOptions {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (21K chars).
[
{
"path": ".gitignore",
"chars": 20,
"preview": ".idea/\nnode_modules/"
},
{
"path": ".npmignore",
"chars": 33,
"preview": "node_modules\nexamples\nbower.json\n"
},
{
"path": "Gruntfile.js",
"chars": 577,
"preview": "module.exports = function (grunt) {\n\n var saveLicense = require('uglify-save-license');\n // project configuration\n gr"
},
{
"path": "LICENSE.md",
"chars": 1079,
"preview": "The MIT License (MIT)\nCopyright (c) 2016 Vadim Namniak\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 3238,
"preview": "# canvas-text-wrapper\n\n\n## Syntax\n```javascript\nCanvasTextWrapper(HTMLCanvasElement, String [, options]);\n```\n\n## Option"
},
{
"path": "bower.json",
"chars": 361,
"preview": "{\n \"name\": \"canvas-text-wrapper\",\n \"version\": \"0.9.2\",\n \"ignore\": [\n \"**/.*\",\n \"**/*.log\",\n \"**/*.json\",\n "
},
{
"path": "canvas-text-wrapper.js",
"chars": 10945,
"preview": "(function (root) {\n\n function CanvasTextWrapper(canvas, text, options) {\n 'use strict';\n\n var defaults = {\n "
},
{
"path": "index.d.ts",
"chars": 1972,
"preview": "\nexport function CanvasTextWrapper(element: HTMLCanvasElement, text: string, options?: CanvasTextWrapperOptions): void;\n"
},
{
"path": "package.json",
"chars": 896,
"preview": "{\n \"name\": \"canvas-text-wrapper\",\n \"description\": \"Split canvas text into lines on specified rule with optional alignm"
},
{
"path": "test/test.js",
"chars": 875,
"preview": "var CanvasTextWrapper = require('../canvas-text-wrapper.js').CanvasTextWrapper;\n\nvar body = document.getElementsByTagNam"
}
]
About this extraction
This page contains the full source code of the namniak/canvas-text-wrapper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (19.5 KB), approximately 5.3k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.