Main API wordcloud-wasm #

Promise-based functions that hide the WASM initialisation dance. Import from the package root.

import
import { layout, treemap, ready, LayoutMode } from "wordcloud-wasm";
fn

ready

ready(input?: unknown): Promise<unknown>

Ensures the WASM module is initialized. Idempotent — the first call starts initialization; every subsequent call awaits the same cached promise. You rarely need to call this directly; layout() and treemap() call it automatically.

ParameterTypeDescription
input unknown (optional) Custom WASM source forwarded to the generated init(). Accepts a URL string, Response, ArrayBuffer, or WebAssembly.Module. Omit in browser environments — the .wasm file is fetched automatically.

Returns: Promise<unknown> — resolves when the module is ready.

example
// Pre-warm the module during page load.
await ready();
Bundler note (Next.js, Vite, Webpack, etc.): if you hit path/404 errors for the .wasm file — especially with a static export — pass an explicit URL instead of relying on the default same-directory fetch. The simplest zero-config fix is pointing at the published package on a CDN, pinned to your installed version:
cdn fallback
import { version } from "wordcloud-wasm/package.json";
await ready(`https://unpkg.com/wordcloud-wasm@${version}/pkg/wordcloud_layout_bg.wasm`);

See the README's Using with a bundler section for CDN vs. self-hosted trade-offs and a Webpack asset/resource config for self-hosting.

fn

layout

layout(items: Item[], options?: LayoutOptions): Promise<Placement[]>

Lays out weighted words and resolves to their computed placements. Words are placed in decreasing weight order; words that do not fit are silently omitted from the output.

ParameterTypeDescription
items Item[] Array of weighted words. Must be a non-empty array.
options LayoutOptions (optional) Canvas dimensions, font range, strategy mode, padding, and render font. See LayoutOptions.

Returns: Promise<Placement[]> — x/y are the bounding-box center; render with textAlign:"center" and textBaseline:"middle".

Throws: TypeError if items is not an array; RangeError for an unknown mode string; an Error if options.font cannot be fetched or parsed.

example
const items = [
  { text: "WebAssembly", weight: 100 },
  { text: "Rust",        weight: 80  },
  { text: "JavaScript",  weight: 60  },
];

const placements = await layout(items, {
  mode:        "pretty",
  width:       800,
  height:      600,
  minFontSize: 14,
  maxFontSize: 80,
  padding:     4,
});
// → [{ text, x, y, rotation, fontSize, baselineOffset }, ...]

const ctx = canvas.getContext("2d");
for (const { text, x, y, rotation, fontSize } of placements) {
  ctx.save();
  ctx.translate(x, y);
  if (rotation) ctx.rotate(rotation * Math.PI / 180);
  ctx.font = `${fontSize}px sans-serif`;
  ctx.textAlign    = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(text, 0, 0);
  ctx.restore();
}

Matching the layout to your render font. The engine has no font of its own, so tell layout which one you draw with and it measures each word in it via canvas measureText — the same code path the browser will use to paint the text, which means kerning, ligatures, font fallback and shaping all come along for free.

matching your render font
const placements = await layout(items, {
  width: 800,
  height: 600,
  font: "Inter, sans-serif",   // defaults to "sans-serif"
  fontWeight: 600,             // optional
});

ctx.textAlign    = "center";
ctx.textBaseline = "alphabetic";
for (const p of placements) {
  ctx.font = `600 ${p.fontSize}px Inter, sans-serif`;
  ctx.fillText(p.text, p.x, p.y + p.baselineOffset);
}

Without a matching measurement the engine falls back to a built-in approximation of a generic sans-serif. Where your real font is wider than that guess, the drawn glyphs overrun the space reserved for them; since placements are center-anchored the excess splits across both ends, appearing as slight overlap at a word's first and last letters.

Measurements are cached per font, so repeated layouts over the same words cost nothing. Pass metrics: null to skip measuring, or your own TextMetrics to supply it from elsewhere.

fn

measureText

measureText(texts: Array<string | Item>, options?: FontOptions): TextMetrics | null

Measures words with a canvas, in the font they will be rendered in. layout() calls this for you — reach for it directly only when the layout runs somewhere the render font is not available.

The main case is a Web Worker: a worker's font set is not the document's, so a font the page loaded at runtime would measure there as whatever fallback the worker resolves to. createLayoutWorker() already handles this by measuring on the main thread before posting.

ParameterTypeDescription
texts Array<string | Item> Words to measure, in the order they will be handed to layout().
options FontOptions (optional) Which font to measure in. Defaults to sans-serif.

Returns: TextMetrics | null — null where no canvas is available to measure with, or where the font string could not be parsed.

example
import { measureText } from "wordcloud-wasm";
import { createLayoutWorker } from "wordcloud-wasm/layout-worker";

const metrics = measureText(items, { font: "Inter, sans-serif" });
const placements = await createLayoutWorker().layout(items, {
  width: 800, height: 600, metrics,
});
fn

treemap

treemap(items: Item[], options?: TreemapOptions): Promise<TreemapRect[]>

Lays out weighted items as a squarified treemap — axis-aligned rectangles whose areas are proportional to weight, tiling the canvas with no gaps or overlaps.

ParameterTypeDescription
items Item[] Array of weighted items.
options TreemapOptions (optional) Canvas dimensions. See TreemapOptions.

Returns: Promise<TreemapRect[]> — x/y are the top-left corner of each tile.

Throws: TypeError if items is not an array.

example
const rects = await treemap([
  { text: "Alpha", weight: 40 },
  { text: "Beta",  weight: 30 },
  { text: "Gamma", weight: 20 },
], { width: 800, height: 600 });
// → [{ text, value, x, y, width, height }, ...]

for (const { text, x, y, width, height } of rects) {
  ctx.strokeRect(x, y, width, height);
  ctx.fillText(text, x + 4, y + 16);
}

Types #

TypeScript interfaces, type aliases, and constants exported from wordcloud-wasm.

interface

Item

A single weighted input word. The engine's Input Model.

TypeScript
interface Item {
  text:   string;  // the word or label
  weight: number;  // relative importance — drives font size and placement order
}
Weights are relative — only their ratios matter. They do not need to sum to any specific value.
interface

Placement

A placed word returned by layout(). Coordinates are the center of the word's bounding box.

TypeScript
interface Placement {
  text:     string;  // the word
  x:        number;  // bounding-box center X (px)
  y:        number;  // bounding-box center Y (px)
  rotation: number;  // 0 or 90 (degrees)
  fontSize: number;  // computed font size (px)
  baselineOffset: number;  // distance from y down to the baseline (px)
}
Rotate around the center point before drawing: ctx.translate(x, y); ctx.rotate(rotation * Math.PI / 180);

baselineOffset is measured by whichever rasterizer sized the word. Drawing with textBaseline:"alphabetic" at y + baselineOffset puts the baseline exactly where the engine reserved space, instead of depending on how a given browser defines "middle". Rendering with "middle" stays close enough for most fonts, so this field is purely additive.

interface

TreemapRect

A treemap tile returned by treemap(). Origin is top-left.

TypeScript
interface TreemapRect {
  text:   string;  // the item label
  value:  number;  // the item's original weight
  x:      number;  // top-left X (px)
  y:      number;  // top-left Y (px)
  width:  number;
  height: number;
}
Use ctx.fillRect(x, y, width, height) — top-left origin, unlike Placement which is centered.
interface

LayoutOptions

FieldTypeDefaultDescription
mode LayoutModeName "pretty" Layout strategy. See Mode Comparison.
width number 1024 Canvas width in pixels.
height number 768 Canvas height in pixels.
minFontSize number 12 Font size (px) assigned to the lowest-weight word.
maxFontSize number 96 Font size (px) assigned to the highest-weight word.
padding number 2 Gap (px) added around each word's footprint to prevent crowding.
font string "sans-serif" CSS font-family words will be rendered in. Words are measured in it so their footprints match what gets drawn.
fontWeight string | number — CSS font-weight, if not normal.
fontStyle string — CSS font-style, if not normal.
metrics TextMetrics | null — Measurements to use instead of measuring here. null skips measuring entirely.
wasm unknown — Custom WASM source forwarded to ready().
interface

TreemapOptions

FieldTypeDefaultDescription
widthnumber1024Canvas width in pixels.
heightnumber768Canvas height in pixels.
wasmunknown—Custom WASM source forwarded to ready().
interface

FontOptions

Which font to measure in. These are assembled into a CSS font shorthand and set on a canvas, so anything a canvas accepts works — including family stacks with fallbacks ("Inter, Helvetica, sans-serif").

FieldTypeDefaultDescription
fontstring"sans-serif"CSS font-family.
fontWeightstring | number—CSS font-weight, if not normal.
fontStylestring—CSS font-style, if not normal.
interface

TextMetrics

Text dimensions measured in the render font, as returned by measureText().

TypeScript
interface TextMetrics {
  ascent: number;    // above the baseline, as a fraction of the font size
  descent: number;   // below the baseline, as a fraction of the font size
  widths: number[];  // footprint width per word, parallel to the items
}

Everything is a fraction of the font size rather than a pixel count, so a single measurement pass serves every size the layout assigns.

widths is positional: entry i describes item i. A short list is safe — items past its end fall back to the built-in approximation rather than erroring.
type

LayoutModeName

"pretty" | "balanced" | "fast"

String alias accepted by LayoutOptions.mode. Case-insensitive. See Mode Comparison for guidance.

const

LayoutMode

The raw WASM enum, re-exported for advanced callers that work directly with the low-level bindings. Prefer the string mode option in LayoutOptions for normal use.

TypeScript
const LayoutMode: {
  readonly Pretty:   0;
  readonly Balanced: 1;
  readonly Fast:     2;
};

Web Worker Helper wordcloud-wasm/layout-worker #

Runs the layout engine in a dedicated Web Worker so the main thread is never blocked by heavy layout work.

import
import { createLayoutWorker } from "wordcloud-wasm/layout-worker";
fn

createLayoutWorker

createLayoutWorker(opts?: LayoutWorkerOptions): LayoutWorker

Creates a LayoutWorker backed by a Web Worker. The worker is spawned immediately; call layoutWorker.terminate() when it is no longer needed.

ParameterTypeDescription
opts LayoutWorkerOptions (optional) Configuration. See LayoutWorkerOptions.

Returns: LayoutWorker

example
const layoutWorker = createLayoutWorker();

const placements = await layoutWorker.layout(items, {
  mode: "balanced", width: 1200, height: 800,
});

layoutWorker.terminate(); // when done
interface

LayoutWorkerOptions

FieldTypeDescription
worker Worker (optional) Bring your own Worker instance. If omitted, a module worker is spawned automatically from worker.js next to layout-worker.js. Useful for custom bundler setups or when controlling the worker URL.
interface

LayoutWorker

MemberTypeDescription
worker Worker The underlying Worker instance, exposed for advanced use (e.g. monitoring onerror).
layout() (items, options?) => Promise<Placement[]> Same signature and semantics as the main-thread layout(), but executes inside the worker. Supports multiple concurrent calls — each call is tracked by a unique ID.
terminate() () => void Terminates the worker immediately. All in-flight layout() calls are rejected.
Worker message protocol (internal — not needed for normal use):
Request: { type: "layout", id, items, options }
Success: { type: "result", id, placements }
Error: { type: "error", id, message }

Multi-threading wordcloud-wasm/threads #

Optional Rayon-based thread pool for the threaded WASM build. The default build is single-threaded and does not require this module. Everything degrades gracefully — if threading is unavailable, initThreads() returns 0.

Prerequisites: (1) Build the threaded pkg — see README. (2) Serve the page with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers. (3) await initThreads() once before the first layout() call.
import
import { threadsSupported, initThreads } from "wordcloud-wasm/threads";
fn

threadsSupported

threadsSupported(): boolean

Returns true when the runtime can host WASM threads — SharedArrayBuffer is available and (in a browser) crossOriginIsolated is true.

fn

initThreads

initThreads(threads?: number): Promise<number>

Starts the WASM Rayon thread pool. Safe to call unconditionally — returns 0 when the threaded build is not loaded, SharedArrayBuffer is unavailable, or cross-origin isolation is missing.

ParameterTypeDefaultDescription
threads number (optional) hardwareConcurrency Desired number of worker threads.

Returns: Promise<number> — the number of threads started, or 0 if threading is unavailable.

example
const n = await initThreads();
console.log(n > 0 ? `Using ${n} threads` : "Single-threaded mode");

// layout() now uses the thread pool automatically.
const placements = await layout(items, { mode: "fast" });

Low-level WASM API pkg/wordcloud_layout.js #

The raw wasm-bindgen generated bindings. Use the main JS API instead for typical use cases. This layer is documented for advanced scenarios: custom bundler integration, direct WASM instantiation, or SSR environments.

import
import init, { layout_words, layout_treemap,
              LayoutMode, ping, version }
  from "wordcloud-wasm/pkg";
fn

init

init(module_or_path?): Promise<InitOutput>

Asynchronously instantiates the WASM module. Must be called and awaited before any other WASM function. Accepts a URL, Response, BufferSource, or a pre-compiled WebAssembly.Module.

fn

initSync

initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput

Synchronously instantiates the WASM module from bytes or a pre-compiled WebAssembly.Module. Use only when async initialization is not possible.

fn

layout_words

layout_words(input: any, mode: LayoutMode, config: any): any

Raw WASM binding for word cloud layout. Deserializes the JS input array and config object, runs the selected strategy, and returns a JS array of placement objects.

ParameterDescription
inputJS array of { text, weight } objects.
modeLayoutMode.Pretty, LayoutMode.Balanced, or LayoutMode.Fast.
configJS object with { width?, height?, minFontSize?, maxFontSize?, padding?, metrics? }. null/undefined yields all defaults.

Returns: JS array of { text, x, y, rotation, fontSize, baselineOffset }. Throws a JS exception on malformed input — never panics.

Pass config.metrics — { ascent, descent, widths }, exactly the shape measureText() returns — to size words from measurements taken in the render font. widths is parallel to input. Without it, words fall back to the built-in metrics approximation.

example
const metrics = measureText(items, { font: "Inter, sans-serif" });
const placements = layout_words(items, LayoutMode.Pretty, { ...cfg, metrics });
fn

layout_treemap

layout_treemap(input: any, config: any): any

Raw WASM binding for treemap layout. Only width and height are read from config; font-size fields are ignored.

Returns: JS array of { text, value, x, y, width, height }.

fn

ping

ping(): string

Liveness check. Returns a human-readable greeting such as "wordcloud_layout v0.3.0 ready". Useful for verifying the WASM module loaded correctly.

fn

version

version(): string

Returns the crate version string, e.g. "0.3.0".

Rust Crate API #

For consumers building Rust applications or extending the engine with new layout strategies. All public types and traits are re-exported from the crate root (crate/src/lib.rs).

trait

LayoutEngine

The core abstraction. A layout strategy is any type that implements this trait. The three word-cloud modes (Pretty, Balanced, Fast) and the treemap are all separate impl LayoutEngine — they share no code with each other, only the underlying infrastructure (OccupancyBitmap, SpatialGrid, WordMask).

Rust
pub trait LayoutEngine {
    type Item;    // input element (e.g. a weighted word)
    type Config;  // canvas / tuning parameters
    type Output;  // computed placement handed to the renderer

    fn layout(&self, items: &[Self::Item], config: &Self::Config) -> Vec<Self::Output>;
}

Custom strategy example:

custom_strategy.rs
use wordcloud_layout::{LayoutEngine, Item, LayoutConfig, Placement};

struct StackAtCenter;

impl LayoutEngine for StackAtCenter {
    type Item   = Item;
    type Config = LayoutConfig;
    type Output = Placement;

    fn layout(&self, items: &[Item], cfg: &LayoutConfig) -> Vec<Placement> {
        let (cx, cy) = (cfg.width / 2.0, cfg.height / 2.0);
        items.iter().map(|it| Placement {
            text: it.text.clone(),
            x: cx, y: cy, rotation: 0, font_size: 12.0,
            baseline_offset: 3.6,
        }).collect()
    }
}

Structs

struct

Item

Rust
pub struct Item {
    pub text:   String,
    pub weight: f32,
}

Weighted text input. Implements Serialize + Deserialize (serde). Matches the JS { text, weight } input model.

struct

Placement

Rust
pub struct Placement {
    pub text:      String,
    pub x:         f32,
    pub y:         f32,
    pub rotation:  u8,  // 0 or 90
    pub font_size: f32,
    // distance (px, downward) from `y` to the text baseline
    pub baseline_offset: f32,
}

Public, JSON-serializable output. Serialized with camelCase keys (fontSize, baselineOffset) to match the JS output model.

struct

LayoutConfig

Rust
pub struct LayoutConfig {
    pub width:        f32,  // default: 1024
    pub height:       f32,  // default: 768
    pub min_font_size: f32, // default: 12
    pub max_font_size: f32, // default: 96
    pub padding:      f32,  // default: 2
    pub metrics: Option<TextMetrics>, // default: None
}

Deserializes from camelCase JS objects (minFontSize, maxFontSize). Implements Default. metrics carries text measured in the caller's own render font; when present, the layout functions size words from it instead of the built-in approximation.

struct

OccupancyBitmap

Canvas-sized bit array tracking occupied pixels. Used by layout strategies for pixel-perfect collision detection.

MethodSignatureDescription
new(width: u32, height: u32) → SelfAllocates a blank bitmap.
collides(&self, mask: &WordMask, dx: i32, dy: i32) → boolTests if placing mask at offset (dx, dy) overlaps any occupied pixel.
occupy(&mut self, mask: &WordMask, dx: i32, dy: i32)Marks all pixels covered by mask at offset (dx, dy) as occupied.
struct

WordMask

Per-word pixel mask. Passed to OccupancyBitmap::collides and occupy.

MethodSignatureDescription
new(width: u32, height: u32) → SelfEmpty mask.
from_pixels(pixels: &[u8], width: u32, height: u32) → SelfBuild from a raw pixel buffer.
get / set(x: u32, y: u32) → bool / (val: bool)Read/write individual pixels.
count_ones() → u32Number of set pixels.
struct

SpatialGrid

Uniform-grid broad-phase spatial index. Used by the Balanced strategy to accelerate neighbor lookups before expensive per-pixel collision checks.

MethodDescription
new(w, h, cell_size)Create a grid for a canvas of w × h pixels.
recommended_cell_size(w, h)Compute a sensible cell size for the given canvas.
insert(id, x, y, w, h)Register word id at bounding box (x, y, w, h).
neighbors(x, y, w, h)Return word IDs in cells overlapping (x, y, w, h).
clear()Reset the grid for reuse.
trait

Rasterizer

Rust
pub trait Rasterizer: Sync {
    fn measure(&self, text: &str, font_size: f32) -> (u32, u32);
    fn baseline_offset(&self, font_size: f32) -> f32;
    fn rasterize(
        &self, text: &str, font_size: f32,
        rotation: Rotation, padding: u32,
    ) -> WordMask;
}

Produces the collision footprint for a word. Sync because mask generation is the layout's main parallelization target — implementations are shared across Rayon workers. Two implementations ship: MetricsRasterizer and MeasuredRasterizer. LayoutConfig::metrics picks between them automatically.

Both produce the same shape of mask — a run of filled boxes, whitespace left empty. Deliberately not glyph contours: contours would let the packer slide one word's ascender into the gap between another word's letters, and the empty space inside a box is much of what keeps a cloud legible.

struct

MetricsRasterizer

The fallback Rasterizer. Each character contributes a filled box sized from a per-character advance-width table, so m/w measure wider than i/l without shipping real font metrics. Needs no font asset and builds anywhere, but it is a guess at whatever font the renderer uses — where it undershoots, drawn glyphs overrun their reserved space and words graze each other at the edges.

struct

MeasuredRasterizer

Rust
impl MeasuredRasterizer {
    pub fn new<'a>(
        metrics: &TextMetrics,
        texts: impl IntoIterator<Item = &'a str>,
    ) -> Self;
}

Boxes sized from measurements the caller took in its own render font. In a browser those come from canvas measureText, which the JS wrapper handles automatically; from Rust, from whatever text stack you already draw with.

The built-in advance table still decides how a word's total width is divided between characters, so whitespace keeps its share and phrases retain their inter-word gaps. Only the overall width is authoritative. baseline_offset reports (ascent - descent) / 2.

Rust
use wordcloud_layout::{layout_pretty, LayoutConfig, TextMetrics};

let config = LayoutConfig {
    metrics: Some(TextMetrics { ascent: 0.905, descent: 0.212, widths }),
    ..LayoutConfig::default()
};
let placements = layout_pretty(&items, &config);
Text with no supplied measurement falls back to MetricsRasterizer, so partial measurements degrade rather than fail.
struct

TextMetrics

Rust
pub struct TextMetrics {
    pub ascent: f32,
    pub descent: f32,
    pub widths: Vec<f32>,
}

Text dimensions measured by the caller, as fractions of the font size so one measurement pass serves every size. widths is parallel to the items slice handed to the layout; entries past its end fall back to the approximation. Carried on LayoutConfig::metrics, and serialized as { ascent, descent, widths } across the WASM boundary.

Enums

enum

Rotation

Rust
pub enum Rotation { None, Ninety }
// Rotation::from_degrees(deg: u8) → Self

Word rotation passed to the Rasterizer. None = 0°, Ninety = 90°.

enum

WeightDist

Rust
pub enum WeightDist { Uniform, Linear, Zipf }

Weight distribution selector for benchmark data generation. Available in crate::datasets.

Free Functions

fn

layout_pretty / layout_balanced / layout_fast

Rust
pub fn layout_pretty  (items: &[Item], cfg: &LayoutConfig) -> Vec<Placement>;
pub fn layout_balanced(items: &[Item], cfg: &LayoutConfig) -> Vec<Placement>;
pub fn layout_fast    (items: &[Item], cfg: &LayoutConfig) -> Vec<Placement>;

Run a specific strategy directly, bypassing the dispatch enum. Words are measured from cfg.metrics when it is present, and with the built-in MetricsRasterizer otherwise.

fn

layout_pretty_with / layout_balanced_with / layout_fast_with

Rust
pub fn layout_pretty_with(
    items: &[Item], cfg: &LayoutConfig,
    rasterizer: &dyn Rasterizer,
) -> Vec<Placement>;

The same three strategies, measuring words with an explicit Rasterizer. Only needed when you want to bypass LayoutConfig::metrics and hand in a rasterizer directly. The WithRasterizer { mode, rasterizer } wrapper offers the same thing through the LayoutEngine trait.

fn

rasterize_word

Rust
pub fn rasterize_word(
    text: &str, font_size: f32,
    rotation: u8, padding: u32,
) -> WordMask;

Rasterize a single word into a WordMask using the built-in MetricsRasterizer.

fn

datasets::generate / datasets::config_for

Rust
pub fn generate(n: usize, dist: WeightDist, seed: Option<u64>) -> Vec<Item>;
pub fn config_for(n: usize) -> LayoutConfig;

Deterministic benchmark helpers. generate produces synthetic items; config_for returns a canvas configuration appropriate for n words.

Error Handling #

Both layout() and treemap() validate their inputs before entering WASM and throw synchronously on bad arguments:

ErrorCondition
TypeError: items must be an array items argument is not an array.
RangeError: unknown layout mode options.mode is not "pretty", "balanced", or "fast".
Exception from WASM Malformed individual items (e.g. missing text field). The WASM module never panics on user input — all errors become JS exceptions.

Since the functions return Promise, WASM errors arrive as rejected promises. Always wrap in try/catch:

error-handling.js
try {
  const placements = await layout(items, options);
} catch (err) {
  console.error("Layout failed:", err.message);
}

Layout Modes — Comparison #

Mode String Visual Quality Speed Best for
Pretty "pretty" ★★★ Medium Presentations, final renders, small–medium word counts
Balanced "balanced" ★★★ Slower Large word counts (1000+), maximum density
Fast "fast" ★★ ★★★ Live previews, real-time updates, very large datasets
pretty

Archimedean spiral placement

Words spiral outward from the canvas center, largest first. Uses pixel-perfect OccupancyBitmap collision detection for tight, organic-looking clouds. Best quality for typical word counts.

balanced

Spiral + spatial grid

Same spiral as Pretty, augmented with a SpatialGrid broad-phase filter that rejects distant candidates without per-pixel checks. Handles 1000+ words efficiently while maintaining high visual quality.

fast

Shelf packing

Arranges words in horizontal rows by decreasing font size. Less organic-looking but dramatically faster — ~120× faster than d3-cloud at 100 words, ~180× at 5000 words. Ideal for live previews or very large datasets.

All three modes accept identical input/output types and are interchangeable — switch mode at any time without changing any other code.