npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ultimate-text-to-image

v1.0.1

Published

Generate UTF8 texts into image with auto line break for all international language, including Chinese, Japanese, Korean, etc..

Downloads

3,938

Readme

Ultimate Text To Image

Generate Unicode texts into image with auto line break for all international language, including Chinese, Japanese, Korean, Russian, etc.. You can find a lot of similar library, but hardly find any library deal with utf8 texts with auto wrapping.

NPM version

Thanks to linebreak, we can detect the correct line break and wrapping with ease. The actual depended package is linebreak-next, which is compatible with Browser and provided by the collaborator of the original package.

This library depends on canvas for rendering. Please refer to their doc for further optimization. A canvas object can be modified during the rendering phase.

sample

Feature Highlights

  • auto wrap for unicode texts (Chinese, Korean, Japanese, etc...)
  • support adding image with various position & repeat features
  • perfect alignment of texts by detecting the actual drawing pixels of glyphs
  • library works for NodeJs & browser, react component is provided
  • combining multiple text images into one single image
  • auto reduce font size to fit the given image size
  • high performance, generating a 100 words image only takes around 2ms.
  • works in both JS & typescript

Quick Start

import { UltimateTextToImage} from "ultimate-text-to-image";
// const {UltimateTextToImage} = require("ultimate-text-to-image");

new UltimateTextToImage(`abc xyz 0123456789 零一二三四五六七八九`, {width: 150, fontFamily: "Arial, Sans"})
    .render()
    .toFile("image1.png");

sample

Advanced

import path from "path";
import {getCanvasImage, HorizontalImage, registerFont, UltimateTextToImage, VerticalImage} from "ultimate-text-to-image";
// const path = require("path");
// const {getCanvasImage, HorizontalImage, registerFont, UltimateTextToImage, VerticalImage} = require("ultimate-text-to-image");

// render the image
const textToImage = new UltimateTextToImage("Ultimate Text To Image", {
    width: 400,
    maxWidth: 1000,
    maxHeight: 1000,
    fontFamily: "Arial",
    fontColor: "#00FF00",
    fontSize: 72,
    minFontSize: 10,
    lineHeight: 50,
    autoWrapLineHeightMultiplier: 1.2,
    margin: 20,
    marginBottom: 40,
    align: "center",
    valign: "middle",
    borderColor: 0xFF000099,
    borderSize: 2,
    backgroundColor: "0080FF33",
    underlineColor: "#00FFFF33",
    underlineSize: 2,
});
textToImage.render().toFile(path.join(__dirname, "image2.png"));

// properties
const width = textToImage.width; // final canvas size
const height = textToImage.height;  // final canvas size
const renderedTime = textToImage.renderedTime; // rendering time of canvas
const measuredParagraph = textToImage.measuredParagraph; // all the details of the texts in size
const canvas = textToImage.canvas; // the node-canvas
const hasRendered = textToImage.hasRendered; // a flag to indicate if render() has run

// render again (this will create a new canvas)
const options = textToImage.options.fontFamily = "Comic Sans MS";
const buffer = textToImage.render().toBuffer("image/jpeg");

sample

Export

const textToImage = new UltimateTextToImage("Hello World").render();
const dataUrlPng = textToImage.toDataUrl(); // image/png by default
const dataUrlJpeg = textToImage.toDataUrl("image/jpeg", {quality: 80});

const buffer = textToImage.toBuffer(); // png by default
const bufferPng = textToImage.toBuffer("image/png", {compressionLevel: 6});
const bufferJpeg = textToImage.toBuffer("image/jpeg", {quality: 80, progressive: true});

const file = textToImage.toFile(path.join(__dirname, "imageE1")); // png by default
const filePng = textToImage.toFile(path.join(__dirname, "imageE2.png")); // detect by file extension
const fileJpeg = textToImage.toFile(path.join(__dirname, "imageE3.jpeg"), "image/jpeg", {quality: 80});

const streamPng = textToImage.toStream(); // png by default
const streamJpeg = textToImage.toStream("image/jpeg"); // png by default

// save by stream
const out = fs.createWriteStream(path.join(__dirname, "imageE4.png"));
streamPng.pipe(out);
await new Promise((resolve, reject) => out.on("finish", resolve).on("error", reject));

Adding Images

const url = "https://i.imgur.com/EP7lGGu.jpg";
const buffer = new UltimateTextToImage("repeatX").render().toBuffer();
const base64 = new UltimateTextToImage("fitY").render().toBuffer().toString("base64");
const arrayBuffer = new Uint8Array(new UltimateTextToImage("repeat").render().toBuffer());

// images
const canvasImage1 = await getCanvasImage({url});
const canvasImage2 = await getCanvasImage({base64});
const canvasImage3 = await getCanvasImage({buffer});
const canvasImage4 = await getCanvasImage({arrayBuffer});

// use various way to draw the image
const textToImage = new UltimateTextToImage("Image Example", {
    width: 600,
    height: 600,
    alignToCenterIfLinesLE: 1,
    fontSize: 72,
    backgroundColor: "#FFFFFF99",
    images: [
        {canvasImage: canvasImage1, layer: -1, repeat: "fit"},
        {canvasImage: canvasImage2, layer: 0, repeat: "fitY", x: 10, y: 10, width: 100, height: 100},
        {canvasImage: canvasImage3, layer: 1, repeat: "repeatX", sx: -400, sy: 100, width: 300, height: 300},
        {canvasImage: canvasImage4, layer: 1, repeat: "repeat", sx: -200, sy: -300, tx: -50, ty: -50},
    ],
})
.render()
.toFile(path.join(__dirname, "image3.png"));

sample

Pre Render / Post Render

// ICanvas is alias of Canvas in "canvas" package,
import {UltimateTextToImage, ICanvas} from "ultimate-text-to-image";

const textToImage = new UltimateTextToImage("Rendering", {}, {
    preRender: (canvas: ICanvas) => {
        // before drawing anything on the canvas (when render() is called)
    },
    posRender: (canvas: ICanvas) => {
        // after drawing all the texts and images
    },
}).render();

Register Font

import path from "path";
import {UltimateTextToImage, registerFont} from "ultimate-text-to-image";

// register font
registerFont("./tests/fonts/NotoSansTC-Regular.otf");

// render the image
new UltimateTextToImage("Noto Sans TC:\n床前明月光,\n疑是地上霜,\n舉頭望明月,\n低頭思故鄉。", {fontFamily: "Noto Sans TC", fontSize: 30, fontStyle: "italic"})
    .render()
    .toFile(path.join(__dirname, "image4.png"));

sample

Mixed Image

import {UltimateTextToImage, HorizontalImage, VerticalImage} from "ultimate-text-to-image";

const textToImage1 = new UltimateTextToImage("Image1",
    {backgroundColor: "#0080FF33", fontSize: 60});
const textToImage2 = new UltimateTextToImage("Image2 ".repeat(10).trim(),
    {maxWidth: 200, backgroundColor: "#00FF0033", fontSize: 40});

const horizontalImage = new HorizontalImage([
    textToImage1,
    textToImage2,
    new HorizontalImage([
        new UltimateTextToImage("Horizontal 1"),
        new UltimateTextToImage("Horizontal 2", {fontSize: 50}),
    ]),
    new VerticalImage([
        new UltimateTextToImage("Vertical 1"),
        new UltimateTextToImage("Vertical 2", {fontColor: "#FF0000"}),
    ]),
], {valign: "bottom", backgroundColor: "#AAAAAA", margin: 100});

horizontalImage.render().toFile(path.join(__dirname, "imageMixed1.jpg"));

sample

React


import {UltimateTextToImageComponent} from "ultimate-text-to-image/build/UltimateTextToImageComponent";
// const {UltimateTextToImageComponent} = require("ultimate-text-to-image/build/UltimateTextToImageComponent");

function Component() {
    return <UltimateTextToImageComponent text="Hello World" fontSize={30}/>
}

Options for UltimateTextToImage

| Name | Type | Default | Usage | | --- | --- | --- | --- | width|number| |width of the canvas height|number| |height of the canvas maxHeight|number|undefined|dynamically expand the canvas height to fill more texts noAutoWrap|boolean|false |disable auto wrap fontFamily|string|Arial|font family bold|boolean / string|false |bold, bolder, lighter, 100-900. set it true for bold italic|boolean / string|false |italic, oblique, set it to true for italic fontSize|number|24|font size minFontSize|number|0 |choose a smaller font size to fill all the texts within the canvas fontColor|number / string|#000000 |RRGGBBAA: 0xFF0000: 100% red, 0xFF000080: 50% red, "#999999": 100% gray, "#999919": 10% gray strokeSize|number|0 |stroke size strokeColor|number / string|#000000 |same as fontColor lineHeight|number|0 |line height. default to fontSize lineHeightMultiplier|number|0 |line height = final font size * this value autoWrapLineHeight|number|0 |auto wrap line height autoWrapLineHeightMultiplier|number|0 |auto wrap line height = final font size * this value margin|number|0 |margin marginLeft|number|undefined|margin left. use margin if undefined marginTop|number|undefined|margin top. use margin if undefined marginRight|number|undefined|margin right. use margin if undefined marginBottom|number|undefined|margin bottom. use margin if undefined useGlyphPadding|boolean|true|use the actual rending pixels for alignments chopOverflow|boolean|false |chop rendering overlapping the margin align|string|left|horizontal alignment: left, center, right valign|string|top|vertical alignment: top, middle, bottom alignToCenterIfHeightLessThan|number|0 |align to center if final texts rendered height <= this value alignToCenterIfLinesLessThan|number|0 |align to center if total lines <= this value nestedAlign|string|left|used when it's put inside VerticalImage nestedValign|string|top|used when it's put inside HorizontalImage borderColor|number / string|#000000 |same as fontColor borderSize|number|0 |border size backgroundColor|number / string|""|same as fontColor underlineSize|number|0 |underline size underlineColor|number / string|#000000 |same as fontColor images|array|[]|see below image.canvasImage|CanvasImage| |get the CanvasImage object by calling getCanvasImage(). Please refer to the coding sample image.layer|number|0|0: below background color and border. 1: below the texts, 2: above the texts image.repeat|string|topLeft|fit, fitX, fitY, repeat, repeatX, repeatY, topLeft, topCenter, topRight, middleLeft, etc.. image.x|number|undefined|x = 0 if sx is not set image.y|number|undefined|y = 0 if sy is not set image.sx|number|undefined|if sx < 0, x = canvas width + sx, else x = sx image.sy|number|undefined|if sy < 0, y = canvas height + sy, else y = sy image.tx|number|undefined|if tx < 0, width = tx - x, else width = canvas width + tx - x image.ty|number|undefined|if ty < 0, height = ty - y, else height = canvas height + ty - y image.width|number / string|undefined|width = canvas width - x if width is not set. "width" = image width, "canvas" = canvas width image.height|number / string|undefined|height = canvas height - x if height is not set. "height" = image height, "canvas" = canvas height

Options for VerticalImage / Horizontal Image

VerticalImage and Horizontal only supports the following options

  • backgroundColor
  • align
  • valign
  • nestedAlign
  • nestedValign
  • margin

Notes on glyph padding

By default, useGlyphPadding is true. Meaning it will take the padding size into account.

Here are the examples with or without useGlyphPadding

new UltimateTextToImage("Agjpqy!", {useGlyphPadding: false, underlineSize: 1, backgroundColor: "#00FFFF99"})

sample

new UltimateTextToImage("Agjpqy!", {useGlyphPadding: true, underlineSize: 2, backgroundColor: "#00FFFF99"})

sample

In Browser, we are able to measure the top, right, bottom and left padding precisely. While on server side, left and right padding cannot be returned. There will be slightly different on how texts are aligned. You may need to add some margin for some cases as well.

Notes on using Font

You can use fontFamily: "Arial, Sans, Noto Sans TC" in such way support fall back fonts.

The registeredFont() provided by Canvas is not very clear documented, and I am not expert on fonts. Therefore, I have made various trial and error to draw follow conclusions and suggestions

Considered that you have the Noto Sans TC Regular and Bold Fonts https://fonts.google.com/specimen/Noto+Sans+TC#license

You can use the fonts in the following way:

import {registerFont} from "ultimate-text-to-image";
registerFont("NotoSansTC-Regular.otf", {family: "aliasName", weight: 100}); // fontName: Noto Sans TC
registerFont("NotoSansTC-Bold.otf", {family: "aliasName", weight: "800"}); // fontName: Noto Sans TC

// we use alias font name
// this is just for mapping, it won't really apply bold or italic on the texts, it will just use the font itself
new UltimateTextToImage("text", {fontFamily: "aliasName"}); // Regular
new UltimateTextToImage("text", {fontFamily: "aliasName", fontWeight: 200}); // Regular
new UltimateTextToImage("text", {fontFamily: "aliasName", fontWeight: 400}); // Regular
new UltimateTextToImage("text", {fontFamily: "aliasName", fontWeight: 500}); // Bold
new UltimateTextToImage("text", {fontFamily: "aliasName", fontWeight: 500, fontStyle: "italic"}); // Regular, but the final text has no italic

// we use the actual font name (either loaded in run time or exist in the system)
// this will apply bold / italic if the fonts support
new UltimateTextToImage("text", {fontFamily: "Noto Sans TC", fontWeight: "bold", fontStyle: "italic"}); // final text has bold and italic

However, there can be some unexpected behaviour for different fonts

// remember to comment previous codes to understand the actual behavior
// registerFont("NotoSansTC-Regular.otf", {family: "aliasName", weight: 100});
// registerFont("NotoSansTC-Bold.otf", {family: "aliasName", weight: "800"});
registerFont("NotoSansTC-Thin.otf", {family: "aliasNameThin"}); // fontName: Noto Sans TC Thin
registerFont("NotoSansTC-Medium.otf", {family: "Noto Sans TC Medium"}); // fontName: Noto Sans Medium

// failed to get the font and produce warning: Pango-WARNING **: 11:59:43.357: couldn't load font "Noto Sans TC 80px", falling back to "Sans 80px", expect ugly output.
// The reason probablyl due to the fact that the canvas library incorrectly mapped the aliasNameThin into "Noto Sans TC", 
// while the actualy name is "Noto Sans TC Thin" (I guess the canvas library used wrong meta to do the mapping)
// if you uncomment registerFont("NotoSansTC-Regular.otf", {family: "aliasName", weight: 100}); 
// things will work again due to the reason that the library will attemp to make a guess and load another fonts with similar name
new UltimateTextToImage("text", {fontFamily: "aliasNameThin"}); 

// While this is ok now
new UltimateTextToImage("text", {fontFamily: "Noto Sans TC Thin"}); 

// this will fail. Though you used the correct font name, but the font name is same as the mapping name,
// the library will use the mapping name and incorrect map into "Noto Sans TC" again 
// things will work if you use another alias name: registerFont("NotoSansTC-Medium.otf", {family: "aliasnNameMedium"})
new UltimateTextToImage("text", {fontFamily: "Noto Sans TC Medium"}); 

** Testing above is done on Windows, the behaviors may be different on Linux

As a final conclusion, I you to install the font into system and use actual font name to reduce any unclear outcome. You can still use registerFont, but be sure you did enough testing on it.

Fonts in browser

If you wanted to use more fonts in browser, please check out webfontloader. If you are using the React component, please refresh the component after font loaded.

Performance and caching

The library will cache the measured size of words to achieve responsive rendering. In case you added some new fonts, you can run the following code to clear cache

// cache is stored in run time, restarting your program will also clear the cache
UltimateTextToImage.measurable.clearCache();

Know issues

  • line break checking for emoji may not work perfectly
  • width checking for 8 bytes emoji may have problem

Useful links

  • http://torinak.com/font/lsfont.html
  • https://fonts.google.com/specimen/Noto+Sans+TC#license
  • https://www.npmjs.com/package/canvas
  • https://www.npmjs.com/package/linebreak-next