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

canvas-fill-text-opt

v0.1.5

Published

HTML canvas.fillText() performance optimizations attempts

Downloads

1

Readme

canvas-fill-text-opt

A experimental library attempting to optimize the performance of applications that perform many canvas.fillText() API calls.
It was born out of the frustration of discovering that a desktop 16-core system, in 2022 AD, struggles to display a couple of thousands of strings at 60 FPS inside an HTML canvas.

Installation

npm install canvas-fill-text-opt

Usage

import FillTextOpt from "canvas-fill-text-opt";

let canvas = document.getElementById("my-canvas") as HTMLCanvasElement;
let ctx = canvas.getContext("2d");

let FTO = new FillTextOpt();
await FTO.init(canvas, {
    concurrency: 8,
    textLimitFactor: 1.4,
    fonts: ["Noto Sans", "url(assets/fonts/NotoSans-Regular.ttf)"],
});

// Context settings will be picked up by next fillText() calls, just as normal
ctx.font = '9pt "Noto Sans"';

// Use it as a regular canvas.fillText()
for (let i = 0; i < 2000; i++) {
    FTO.fillText("If you're lucky this frame will be faster!", i, i);
}

// Clip the text inside a 20x20 rectangle
FTO.fillText("My long canvas text, this part won't even be rendered!", 128, 128, null, {
    x: 128,
    y: 128,
    width: 20,
    height: 20,
});

// Finally, actually render everything on the canvas
await FTO.render();

How it works

Parallelism through Web Workers and OffscreenCanvas

The library will try to distribute the fillText() calls among a pool of Web Workers. Every worker creates an OffscreenCanvas and waits for work from the main thread. When the user calls render(), the main thread sends part of the queued fillText calls to every worker, wait for them to return the ImageBitmaps of their internal OffscreenCanvases and finally composites them atop the user canvas.

At initialization, the library will try to detect if the browser supports OffscreenCanvas (https://caniuse.com/offscreencanvas). If it does, sets the pool size to window.navigator.hardwareConcurrency / 2, that is, half the count of the CPU cores.

Thanks to this optimization, an application that performs hundreds or thousands of fillText() calls at every frame was observed to gain a ~20% speedup, given the right conditions.

The size of the workers pool can be changed via the concurrency parameter.

Better text clipping

If needed, the library can clip the rendered text inside a rectangle, if defined as the last parameter of fillText(). When doing so, it will try reduce the number of rendered characters by estimating how many of them can fit in the clip area width, and call canvas.fillText() just on the initial part of the given string, the one that will be actually visible.
The estimation is made by dividing the rectangle width by the average character width (for the current font size and family). The average character width is calculated only once, then cached, using canvas.measureText() on a sample UTF-8 text. The estimated number of characters is finally multiplied by the textLimitFactor parameter, in order to add a bit of "safety margin" to the final result. The default value is 1.4, but it should be tuned depending on the application.

API reference

Just take a look at the FillTextOpt class inside index.ts. It's all there