multiline-canvas-text
Advanced tools
+28
-2
@@ -11,5 +11,31 @@ export interface IFont { | ||
| canvas: HTMLCanvasElement; | ||
| lastCharacterPosition: IPoint; | ||
| cursor: IPoint; | ||
| lines: string[]; | ||
| } | ||
| export declare function drawText(text: string, width: number, fontName: string, fontSize: number, lineSpacing?: number, color?: string): IDrawTextResult; | ||
| export declare const TextAlign: { | ||
| CENTER: string; | ||
| LEFT: string; | ||
| RIGHT: string; | ||
| }; | ||
| /** | ||
| * Creates a canvas of the given width and renders the string into it | ||
| * @param {string} text | ||
| * @param {number} width | ||
| * @param {string} fontName | ||
| * @param {number} fontSize | ||
| * @param {number} lineSpacing | ||
| * @param {string} color | ||
| * @param {boolean} strokeText | ||
| * @param {string} align | ||
| * @returns {IDrawTextResult} | ||
| */ | ||
| export declare function drawText(text: string, width: number, fontName: string, fontSize: number, lineSpacing?: number, color?: string, strokeText?: boolean, align?: string): IDrawTextResult; | ||
| /** | ||
| * Breaks up a string into lines that fit within the supplied width. | ||
| * @param {string} text | ||
| * @param {number} width | ||
| * @param {string} fontName | ||
| * @param {number} fontSize | ||
| * @returns {string[]} | ||
| */ | ||
| export declare function fitText(text: string, width: number, fontName: string, fontSize: number): string[]; |
+131
-22
@@ -5,5 +5,22 @@ "use strict"; | ||
| exports.drawText = drawText; | ||
| exports.fitText = fitText; | ||
| exports.TextAlign = void 0; | ||
| var _trimCanvas = require("./trimCanvas"); | ||
| var TextAlign = { | ||
| CENTER: 'center', | ||
| LEFT: 'left', | ||
| RIGHT: 'right' | ||
| }; | ||
| /** | ||
| * For a given string, returns a new string in which all the separate words (characters divided by a space) fit in the given width. Can add spaces into original words if they are too long. | ||
| * @param {string} text | ||
| * @param {number} availableWidth | ||
| * @param {IFont} font | ||
| * @returns {string} | ||
| */ | ||
| exports.TextAlign = TextAlign; | ||
| function splitIntoFittingWords(text, availableWidth, font) { | ||
@@ -37,3 +54,12 @@ var splitResults = []; | ||
| } | ||
| /** | ||
| * Groups a given string into fitting parts. What a part is is defined by the character to split the original string on. | ||
| * @param {string} text | ||
| * @param {string} splitOn | ||
| * @param {number} availableWidth | ||
| * @param {IFont} font | ||
| * @returns {string[]} | ||
| */ | ||
| function groupText(text, splitOn, availableWidth, font) { | ||
@@ -61,4 +87,17 @@ return text.split(splitOn).reduce(function (resultingLines, currentItem) { | ||
| } | ||
| /** | ||
| * Creates a canvas of the given width and renders the string into it | ||
| * @param {string} text | ||
| * @param {number} width | ||
| * @param {string} fontName | ||
| * @param {number} fontSize | ||
| * @param {number} lineSpacing | ||
| * @param {string} color | ||
| * @param {boolean} strokeText | ||
| * @param {string} align | ||
| * @returns {IDrawTextResult} | ||
| */ | ||
| function drawText(text, width, fontName, fontSize, lineSpacing, color) { | ||
| function drawText(text, width, fontName, fontSize, lineSpacing, color, strokeText, align) { | ||
| if (lineSpacing === void 0) { | ||
@@ -72,25 +111,61 @@ lineSpacing = 0; | ||
| // for now, just add spacing to fix fonts falling ut of view sometimes (at the bottom specifically) | ||
| if (strokeText === void 0) { | ||
| strokeText = false; | ||
| } | ||
| if (align === void 0) { | ||
| align = 'center'; | ||
| } | ||
| var alignModes = [TextAlign.LEFT, TextAlign.CENTER, TextAlign.RIGHT]; | ||
| if (alignModes.indexOf(align) === -1) { | ||
| throw new Error("Invalid alignMode (possible options: " + alignModes.join(',') + ")"); | ||
| } // for now, just add spacing to fix fonts falling ut of view sometimes (at the bottom specifically) | ||
| // padding will be removed by trimming canvas at the end | ||
| var padding = { | ||
| x: 10, | ||
| y: 15 | ||
| }; | ||
| var font = { | ||
| size: fontSize, | ||
| name: fontName | ||
| }; | ||
| var lines = fitText(text, width, font); // create and init canvas | ||
| x: 20, | ||
| y: fontSize * 2 | ||
| }; // todo this needs a better fix | ||
| var font = createFont(fontName, fontSize); | ||
| var lines = fitText(text, width - 2 * padding.x, fontName, fontSize); // create and init canvas | ||
| var canvas = document.createElement('canvas'); | ||
| canvas.width = width + 2 * padding.x; | ||
| canvas.width = width; // + 2 * padding.x; | ||
| canvas.height = lines.length * fontSize + (lines.length - 1) * lineSpacing + 2 * padding.y; | ||
| var context = canvas.getContext('2d'); | ||
| context.font = getCanvasFontProperty(font); | ||
| context.textAlign = 'center'; | ||
| context.textBaseline = 'top'; // draw lines | ||
| context.textAlign = align; | ||
| context.textBaseline = 'top'; | ||
| context.fillStyle = color; | ||
| context.strokeStyle = color; // draw lines | ||
| var centerX = canvas.width * 0.5; | ||
| var baseX; | ||
| switch (align) { | ||
| case TextAlign.RIGHT: | ||
| { | ||
| baseX = canvas.width - padding.x; | ||
| break; | ||
| } | ||
| case TextAlign.LEFT: | ||
| { | ||
| baseX = padding.x; | ||
| break; | ||
| } | ||
| case TextAlign.CENTER: | ||
| { | ||
| baseX = canvas.width * 0.5; | ||
| break; | ||
| } | ||
| } | ||
| var yOffset = 0; | ||
| var lastCharacterPosition = { | ||
| var cursor = { | ||
| x: canvas.width * 0.5, | ||
@@ -100,5 +175,9 @@ y: yOffset | ||
| lines.forEach(function (line) { | ||
| context.fillStyle = color; | ||
| context.fillText(line, centerX, yOffset); | ||
| lastCharacterPosition = { | ||
| if (strokeText) { | ||
| context.strokeText(line, baseX, yOffset); | ||
| } else { | ||
| context.fillText(line, baseX, yOffset); | ||
| } | ||
| cursor = { | ||
| x: canvas.width * 0.5 + 0.5 * getTextWidth(line, font).width, | ||
@@ -110,15 +189,45 @@ y: yOffset | ||
| return { | ||
| lastCharacterPosition: lastCharacterPosition, | ||
| lines: [], | ||
| lines: lines, | ||
| cursor: cursor, | ||
| canvas: (0, _trimCanvas.trimCanvas)(canvas) | ||
| }; | ||
| } | ||
| /** | ||
| * Breaks up a string into lines that fit within the supplied width. | ||
| * @param {string} text | ||
| * @param {number} width | ||
| * @param {string} fontName | ||
| * @param {number} fontSize | ||
| * @returns {string[]} | ||
| */ | ||
| function fitText(text, width, font) { | ||
| function fitText(text, width, fontName, fontSize) { | ||
| var font = createFont(fontName, fontSize); | ||
| var fittingWords = splitIntoFittingWords(text, width, font); | ||
| return groupText(fittingWords, ' ', width, font); | ||
| } | ||
| /** | ||
| * Formats fontName and fontSize into a css string for canvas. | ||
| * @param {IFont} font | ||
| * @returns {string} | ||
| */ | ||
| function getCanvasFontProperty(font) { | ||
| return font.size + "px " + font.name; | ||
| } | ||
| } | ||
| /** | ||
| * Create IFont object | ||
| * @param {string} name | ||
| * @param {number} size | ||
| * @returns {IFont} | ||
| */ | ||
| var createFont = function createFont(name, size) { | ||
| return { | ||
| size: size, | ||
| name: name | ||
| }; | ||
| }; |
+2
-2
| { | ||
| "name": "multiline-canvas-text", | ||
| "version": "1.0.3", | ||
| "description": "Render multiline text to canvas", | ||
| "version": "2.0.0", | ||
| "description": "Render a string to canvas, breaking it up into multiple lines to fit the given width.", | ||
| "main": "./index.js", | ||
@@ -6,0 +6,0 @@ "types": "./index.d.ts", |
+31
-0
| # multiline-canvas-text | ||
| Render a string to canvas, breaking it up into multiple lines to fit the given width. | ||
| ## install | ||
| ```sh | ||
| npm install multiline-canvas-text | ||
| ``` | ||
| ## demo | ||
| Check out the [interactive example](https://petervdn.github.io/multiline-canvas-text/example/). | ||
| ## usage | ||
| ```javascript | ||
| import { drawText } from "multiline-canvas-text"; | ||
| const text = 'The quick brown fox jumps over the lazy dog'; | ||
| const width = 40; // width in pixels to fit the text | ||
| const font = 'Arial'; // font should be available in the page | ||
| const fontSize = 20; // in pixels | ||
| const lineSpacing = 1; // vertical spacing between the lines | ||
| const color = 'white'; // can be any valid css color string: 'black', #FFF', 'rgba(0,0,0,0.5)', etc | ||
| const strokeText = false; // true results in calling strokeText instead of fillText | ||
| const result = drawText(text, width, font, fontSize, lineSpacing, color, strokeText); | ||
| element.appendChild(result.canvas); | ||
| ``` | ||
| The result object contains 3 properties: | ||
| * `canvas`: The generated canvas element with the rendered text. This canvas has the width that was given to the `drawText` method, but can obviously vary in height. | ||
| * `lines`: An array that shows how the string was broken up into multiple lines, for example: `["The quick brown", "fox jumps over", "the lazy dog"]` | ||
| * `cursor`: if you are replicating an interactive textfield in canvas, you may want to add a blinking cursor to increase the user experience. The `cursor` property holds `x` and `y` values for where to draw it. **This value is not correct at the moment!** |
+19
-28
@@ -8,14 +8,6 @@ "use strict"; | ||
| function trimCanvas(canvas) { | ||
| var ctx = canvas.getContext('2d'); // create a temporary canvas in which we will draw back the trimmed text | ||
| var copy = document.createElement('canvas').getContext('2d'); // Use the Canvas Image Data API, in order to get all the | ||
| // underlying pixels data of that canvas. This will basically | ||
| // return an array (Uint8ClampedArray) containing the data in the | ||
| // RGBA order. Every 4 items represent one pixel. | ||
| var pixels = ctx.getImageData(0, 0, canvas.width, canvas.height); // total pixels | ||
| var l = pixels.data.length; // main loop counter and pixels coordinates | ||
| var x; | ||
| var ctx = canvas.getContext('2d'); | ||
| var copy = document.createElement('canvas').getContext('2d'); | ||
| var pixels = ctx.getImageData(0, 0, canvas.width, canvas.height); | ||
| var l = pixels.data.length; | ||
| var y; // an object that will store the area that isn't transparent | ||
@@ -33,4 +25,3 @@ | ||
| if (pixels.data[i + 3] !== 0) { | ||
| // find it's coordinates | ||
| x = i / 4 % canvas.width; | ||
| // find its coordinates | ||
| y = ~~(i / 4 / canvas.width); // store/update those coordinates | ||
@@ -41,16 +32,15 @@ // inside our bounding box Object | ||
| bound.top = y; | ||
| } | ||
| } // if (bound.left === null) { | ||
| // bound.left = x; | ||
| // } else if (x < bound.left) { | ||
| // bound.left = x; | ||
| // } | ||
| // | ||
| // if (bound.right === null) { | ||
| // bound.right = x; | ||
| // } else if (bound.right < x) { | ||
| // bound.right = x; | ||
| // } | ||
| if (bound.left === null) { | ||
| bound.left = x; | ||
| } else if (x < bound.left) { | ||
| bound.left = x; | ||
| } | ||
| if (bound.right === null) { | ||
| bound.right = x; | ||
| } else if (bound.right < x) { | ||
| bound.right = x; | ||
| } | ||
| if (bound.bottom === null) { | ||
@@ -67,6 +57,7 @@ bound.bottom = y; | ||
| var trimHeight = bound.bottom - bound.top; | ||
| var trimWidth = bound.right - bound.left; // get the zone (trimWidth x trimHeight) as an ImageData | ||
| var trimWidth = canvas.width; // do not trim horizontally | ||
| // get the zone (trimWidth x trimHeight) as an ImageData | ||
| // (Uint8ClampedArray of pixels) from our canvas | ||
| var trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight); // Draw back the ImageData into the canvas | ||
| var trimmed = ctx.getImageData(0, bound.top, trimWidth, trimHeight); // Draw back the ImageData into the canvas | ||
@@ -73,0 +64,0 @@ copy.canvas.width = trimWidth; |
177101
2.52%291
61.67%33
1550%