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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@unmade/text-renderer

v1.1.0

Published

Pure function text renderer for SVG output

Readme

@unmade/text-renderer

Pure function SVG text renderer. No side effects, all dependencies injected explicitly. Web worker compatible.

Public API

import { getRenderedText } from '@unmade/text-renderer';

const svg = await getRenderedText({
  text: 'HELLO',
  fontUrl: 'https://example.com/font.ttf',
  physicalSize: [40, 'mm'],
  boxDimensions: {
    width: 500,
    height: 200,
    physicalWidth: 100,
    physicalHeight: 40,
    physicalUnits: 'mm',
  },
  spacing: { outlineWidth: 0, letterSpacing: 0, letterSpacingOutline: 0 },
  baseline: 'flat', // or 'curved', or { type: 'custom', path: '...' }
  horizontalAlignment: 'centre', // 'left' | 'centre' | 'right' | 'distributed'
  verticalAlignment: 'centre',   // 'top' | 'centre' | 'bottom'
  fillColour: { hex: '#000000' },
});

Import paths

import { getRenderedText } from '@unmade/text-renderer';        // Browser
import { getRenderedText } from '@unmade/text-renderer/node';   // Node.js
import { getRenderedText } from '@unmade/text-renderer/worker'; // Web Worker

ESM-only

This package is ESM-only — require('@unmade/text-renderer') will not work. This is a hard constraint: harfbuzzjs (the font shaping engine) uses top-level await for WASM initialisation, which Rollup cannot emit as CJS.

Node.js / Lambda: Node 18+ supports ESM natively, so this is not a blocker. When integrating from a CJS codebase, use dynamic import:

const { getRenderedText } = await import('@unmade/text-renderer/node');

CE Lambda integration: When wiring text-renderer into the Configuration Engine Lambda, use the /node ESM entry (@unmade/text-renderer/node) from an ESM Lambda handler, or use the await import() pattern above from an existing CJS handler. The ce-adapter sub-package (@unmade/text-renderer/ce-adapter) retains a CJS build and can be require()'d normally.

Configuration Engine adapter

The ce-adapter sub-package bridges CE (Configuration Engine) editor state to renderer options:

import { extractTextPlacements, mapStateToRendererOptions } from '@unmade/text-renderer/ce-adapter';

const placements = extractTextPlacements(editor);            // LoadedTextPlacement[]
const options    = mapStateToRendererOptions(placements[0]); // GetRenderedTextOptions
const svg        = await getRenderedText(options);

extractTextPlacements reads an editor instance and returns one LoadedTextPlacement per text placement, resolving fonts, colours, and position data from the CE state.

mapStateToRendererOptions converts a LoadedTextPlacement into the GetRenderedTextOptions shape that getRenderedText accepts — including alignment normalisation (CE uses American English, TR uses British English).

Comparison system

The packages/tr-comparison package provides a pipeline for validating TR output against the CE's own rendering for real production designs.

How it works

  1. A design URL is POSTed to the comparison Lambda
  2. The Lambda loads the design via the CE factory, extracts all text placements, and renders each one with both CE and TR
  3. Both outputs are rasterised to PNG and diffed pixel-by-pixel with pixelmatch
  4. Results (match score, images, state) are stored in S3 and indexed in DynamoDB
  5. A browser comparison is also run via Puppeteer against the deployed demo app
  6. The comparison dashboard at packages/tr-comparison/src displays all results

Stored S3 artefacts

For each placement comparison:

comparisons/{designUrlHash}/{timestamp}/{placementId}/
  ce.png                # Config Engine render
  tr.png                # Text Renderer render
  diff.png              # Pixel difference heatmap
  state.json            # LoadedTextPlacement (CE state passed to the adapter)
  renderer-options.json # GetRenderedTextOptions (mapped TR input)

Regression tests

Regression tests live in __tests__/regression.test.ts and use fixture pairs in __tests__/fixtures/regression/:

| File | Contents | |------|----------| | {name}.json | Fixture metadata + LoadedTextPlacement | | {name}.png | Reference PNG (expected TR render) |

The test pipeline for each fixture:

LoadedTextPlacement
  → mapStateToRendererOptions()
  → getRenderedText()
  → SVG → PNG (resvg-js)
  → pixelmatch diff against {name}.png
  → fail if diff > 1%

Adding a fixture

  1. Run a comparison in the dashboard against a design URL
  2. Select the record and click Save as fixture in the detail panel
  3. Enter a descriptive name (e.g. curved-jersey-40mm) — two files download
  4. Place both files in __tests__/fixtures/regression/
  5. Run npm test to confirm the fixture passes, then commit

Updating fixtures after an intentional change

UPDATE_FIXTURES=true npm test

This re-renders all fixtures and overwrites the stored PNGs. Review the diffs, then commit.

Regression tests require network access to fetch fonts from their CDN URLs.