DENSE TEXT INFRASTRUCTURE FOR PIXIJS 8

Render text at
scene scale.

One retained layer for a million labels, compact glyph batches, incremental updates, CJKV and complex-script shaping, and camera-aware culling across WebGL and WebGPU.

Resident labels
1,000,000
Language path
CJKV + 7 scripts
Fixed CPU store
72 MiB
Renderer path
WebGL 2 + WebGPU
Preparing the renderer…

01 / START

Keep the public surface small.

Create labels synchronously, then publish accepted work through one explicit commit boundary. Stable identities keep hot updates compact.

Install

Terminal
bun add pixi-glyphflow pixi.js pixi-viewport

Create one retained layer

TypeScript
import { Application } from "pixi.js";
import { TextLayer } from "pixi-glyphflow";

const app = new Application();
await app.init({
  resizeTo: window,
  preference: ["webgpu", "webgl"],
  webgl: { preferWebGLVersion: 2 },
});
document.body.appendChild(app.canvas);

const labels = new TextLayer({
  renderer: app.renderer,
  initialCapacity: 1_000_000,
  culling: {
    bounds: { x: 0, y: 0, width: 1280, height: 720 },
    padding: 32,
  },
});
app.stage.addChild(labels);

labels.create({
  text: "Shanghai 24 C",
  x: 24,
  y: 32,
  style: { fontFamily: "Inter", fontSize: 18, fill: 0xffffff },
});

await labels.commit();

Vite builds use an ES module worker and an ES2022 target for the lazy HarfBuzz pipeline. This site runs that production configuration.

02 / EXAMPLES

Add the state each label needs.

Every label starts with text. Position, visibility, groups, layout, shaping, and style remain optional layers that can arrive during creation or later updates.

Create with one required field

Minimal label
const id = labels.create({
  text: "Hello, Glyphflow",
});

await labels.commit();

Coordinates default to the origin, visibility defaults to true, and the renderer uses its standard text style.

Hide a separately created group or one TextId

Visibility composition
const stationSigns = labels.createGroup();

labels.create({ text: "Entrance", group: stationSigns });
const exit = labels.create({ text: "Exit", group: stationSigns });

labels.setGroupVisible(stationSigns, false);
await labels.commit();

labels.setGroupVisible(stationSigns, true);
labels.update(exit, { visible: false });
await labels.commit();

Group masks preserve each member's local visible flag. Every createGroup call returns a fresh identity owned by the layer.

Opt into vertical flow and appearance

Vertical styled label
labels.create({
  text: "入口",
  x: 80,
  y: 40,
  layout: { writingMode: "vertical-rl" },
  style: {
    fontSize: 24,
    fontWeight: "700",
    fill: 0x38bdf8,
  },
});

await labels.commit();

Vertical labels stack upright glyphs from top to bottom. Explicit lines form columns from right to left.

03 / VIEWPORT

Camera work stays camera work.

The binding converts visible viewport corners into layer-local bounds and coalesces drag, inertia, wheel, pinch, zoom, and rotation into one culling commit per frame.

Bind the camera

TypeScript
import { Viewport } from "pixi-viewport";
import { bindViewport } from "pixi-glyphflow/viewport";

const viewport = new Viewport({
  screenWidth: app.screen.width,
  screenHeight: app.screen.height,
  worldWidth: 18_000,
  worldHeight: 12_000,
  events: app.renderer.events,
});

viewport.drag().decelerate().wheel().pinch();
app.stage.addChild(viewport);

const binding = bindViewport(labels, viewport, { addChild: true });
await binding.whenIdle();

Move 100,000 labels

TypeScript
const movingIds = new Float64Array(100_000);
const positions = new Float32Array(200_000);

// Fill identities from createMany() and write packed x/y pairs.
labels.updatePositions(movingIds, positions);
await labels.commit();

console.table({
  revision: labels.stats.revision,
  visible: labels.stats.visibleLabelCount,
  glyphs: labels.stats.submittedGlyphs,
});

01 Camera frames preserve label revisions and shaped glyph runs.

02 Packed Float32 coordinates keep the movement path allocation-light.

03 Every binding listener leaves through one idempotent destroy path.

04 / FONTS

Shape the language. Keep the font choice explicit.

Binary fonts travel through HarfBuzz and glyph-ID MSDF generation. This live chain covers CJKV, Arabic, Devanagari, Hebrew, Thai, Greek, Cyrillic, Vietnamese, and emoji.

Register a custom CJKV font

TypeScript
const productFonts = [
  ["Product CJKV", "/fonts/product-cjkv.ttf"],
  ["Product Arabic", "/fonts/product-arabic.ttf"],
  ["Product Devanagari", "/fonts/product-devanagari.ttf"],
  ["Product Hebrew", "/fonts/product-hebrew.ttf"],
  ["Product Thai", "/fonts/product-thai.ttf"],
] as const;

await Promise.all(
  productFonts.map(async ([family, url]) => {
    const source = new Uint8Array(
      await fetch(url).then((response) => response.arrayBuffer()),
    );
    await labels.fonts.register({ family, source });
  }),
);

labels.fonts.registerFallback("global-ui", [
  ...productFonts.map(([family]) => family),
  "system-ui",
  "PingFang SC",
  "Hiragino Sans",
  "Apple SD Gothic Neo",
  "sans-serif",
]);

labels.create({
  text: "日本語 · 東京テキスト",
  style: { fontFamily: "global-ui", fontSize: 24 },
  shaping: {
    language: "ja",
    script: "Jpan",
    features: ["kern", "liga"],
    variations: { wght: 560 },
  },
});

await labels.commit();

01 Language and script tags select localized OpenType glyphs.

02 Glyph ID coverage advances through custom binary families in order.

03 CSS family stacks reach PixiJS layout and Canvas rasterization intact.

05 / PERFORMANCE

Measured under pressure.

Committed Chrome and WebGL 2 artifacts use isolated processes, GPU completion, warmup frames, and p95 reporting on an Apple M1 Pro.

Reference performance results
WorkloadScaleFrame p95
Million-label viewport1,000,000 resident5.20 ms
Viewport drag + inertia1,000,000 resident5.40 ms
Wheel + pinch zoom1,000,000 resident7.10 ms
Position storm100,000 packed moves9.00 ms
Dynamic counters100,000 text + position14.80 ms
Read the benchmark method and raw-artifact index

06 / ARCHITECTURE

One revision, four bounded stages.

Each stage owns a deep implementation boundary. The application learns one label model and one commit lifecycle.

  1. 01
    StoreDense identities + dirty journal
  2. 02
    ShapeLayout + HarfBuzz worker
  3. 03
    ResidentAtlas + glyph instances
  4. 04
    SubmitWebGL / WebGPU adapter

07 / API

Focused entry points.

Core usage stays on the root import. Optional integrations remain isolated so applications carry the surfaces they use.

Package paths

pixi-glyphflow
TextLayer, FontRegistry, and primary types
pixi-glyphflow/viewport
Frame-coalesced pixi-viewport binding
pixi-glyphflow/shaping
HarfBuzz main-thread and worker shapers
pixi-glyphflow/accessibility
Sparse semantic DOM mirror
pixi-glyphflow/advanced
Atlas, mesh, layout, upload, and spatial primitives

TextLayer essentials

create(spec)
Create a label; text is the required field.
createMany(specs)
Create a validated batch and return stable TextIds.
createGroup()
Create one unique layer-local TextGroupId.
update(id, patch)
Change selected fields for one stable identity.
setGroupVisible(group, visible)
Apply one mask while retaining label-local visibility.
updatePositions(ids, xy)
Apply packed position changes in one columnar pass.
updateTextPositions(ids, text, xy)
Broadcast dynamic text with packed positions.
showAll() / hideAll()
Toggle every resident label through one columnar mutation.
commit()
Publish one monotonic revision through render and culling work.
setViewportBounds(bounds)
Select the resident subset submitted to the renderer.
stats
Read immutable capacity, culling, upload, draw, and timing diagnostics.

08 / GUIDES

Follow the operating path.

The maintained Markdown set carries complete contracts, compatibility boundaries, benchmark evidence, and migration detail.