Modern 3D text rendering for Babylon.js with variable font weights and accessibility features
npm install meshwriter-cuduModern 3D text rendering for Babylon.js 8+ with ES modules, variable font weights, and accessibility features.


- Babylon.js 6-8+ support with async/sync APIs for CSG2 compatibility
- ES modules with full tree-shaking support
- Variable font weights via build-time baking for personalized typography
- Dyslexia-friendly fonts including Atkinson Hyperlegible Next with kerning
- TypeScript declarations included
- WCAG color contrast utilities for accessible text rendering
- Fog support for emissive text via custom material plugin
``bash`
npm install meshwriter-cudu
Peer dependencies:
`bash`
npm install @babylonjs/core earcut
`javascript
import { MeshWriter, registerFont } from 'meshwriter-cudu';
import helvetica from 'meshwriter-cudu/fonts/helvetica';
// Register a font
registerFont('Helvetica', helvetica);
// Create writer (async for Babylon 8+)
const Writer = await MeshWriter.createAsync(scene);
// Create 3D text
const text = new Writer("Hello World", {
'font-family': 'Helvetica',
'letter-height': 50,
'letter-thickness': 5,
color: '#1C3870'
});
// Get the mesh for manipulation
const mesh = text.getMesh();
`
| Babylon Version | API | Notes |
|-----------------|-----|-------|
| 6.x - 7.30 | MeshWriter.create(scene, prefs) | Sync, uses legacy CSG |await MeshWriter.createAsync(scene, prefs)
| 7.31+ / 8.x | | Async, uses CSG2 |
Recommendation: Use createAsync for all new projects. It handles CSG initialization automatically and works across all supported Babylon versions.
`javascript
import { registerFont, registerFontAliases, isFontRegistered, getRegisteredFonts } from 'meshwriter-cudu';
// Register font
registerFont('MyFont', fontSpec);
// Create aliases for the same font
registerFontAliases('MyFont', 'myfont', 'my-font');
// Check registration
isFontRegistered('MyFont'); // true
getRegisteredFonts(); // ['MyFont', 'myfont', 'my-font', ...]
`
`javascript
new Writer("Text", {
// Positioning
position: { x: 0, y: 0, z: 0 },
anchor: 'left' | 'center' | 'right',
// Sizing
'letter-height': 50, // Height in world units
'letter-thickness': 5, // Depth/extrusion
// Appearance
color: '#FF0000', // Emissive color (hex)
alpha: 1.0, // Transparency (0-1)
'emissive-only': false, // Disable lighting (self-lit)
'fog-enabled': true, // Affected by scene fog
// Material colors (advanced)
colors: {
diffuse: '#FFFFFF',
specular: '#FFFFFF',
ambient: '#000000',
emissive: '#FF0000'
},
// Font
'font-family': 'Helvetica'
});
`
`javascript
const text = new Writer("Hello");
// Get meshes
text.getMesh(); // Combined mesh
text.getSPS(); // SolidParticleSystem
text.getMaterial(); // StandardMaterial
// Appearance
text.setColor('#00FF00'); // Change color
text.setAlpha(0.5); // Change transparency
text.color('#00FF00'); // Get/set color
text.alpha(0.5); // Get/set alpha
// Layout info
text.getLetterCenter(0); // Center of first letter
text.getLettersBoxes(); // Bounding boxes
text.getLettersOrigins(); // Origin positions per letter
text.getOffsetX(); // Anchor offset
// Cleanup
text.dispose();
text.isDisposed();
`
`javascript`
MeshWriter.isReady(); // Check if CSG is initialized
MeshWriter.getCSGVersion(); // Returns 'CSG2', 'CSG', or null
MeshWriter.setCSGInitializer(fn); // Custom CSG2 initializer
MeshWriter.setCSGReadyCheck(fn); // Custom readiness check
MeshWriter.onCSGReady(callback); // Run code when CSG is ready
MeshWriter.markCSGReady(); // Manually flag CSG ready
| Font | Import Path | Notes |
|------|-------------|-------|
| Helvetica | meshwriter-cudu/fonts/helvetica | Classic sans-serif |meshwriter-cudu/fonts/comic-sans
| Comic Sans | | Display font |meshwriter-cudu/fonts/jura
| Jura | | Modern geometric |meshwriter-cudu/fonts/hiruko-pro
| Hiruko Pro | | Japanese-compatible |meshwriter-cudu/fonts/webgl-dings
| WebGL Dings | | Symbols |meshwriter-cudu/fonts/atkinson-hyperlegible
| Atkinson Hyperlegible | | Dyslexia-friendly |meshwriter-cudu/fonts/atkinson-hyperlegible-next
| Atkinson Hyperlegible Next | | Enhanced with kerning |
`javascript
import helvetica from 'meshwriter-cudu/fonts/helvetica';
import atkinson from 'meshwriter-cudu/fonts/atkinson-hyperlegible-next';
registerFont('Helvetica', helvetica);
registerFont('Atkinson', atkinson);
`
MeshWriter supports variable font weights for accessibility applications, allowing users to select their preferred font weight for improved readability.
Generate FontSpec JSON files at build time for zero runtime overhead:
#### 1. Bake fonts during build
`bashDefault: weights 200-800 in steps of 50, ASCII keyboard characters
npm run bake:fonts
#### 2. Load at runtime
`javascript
import { loadBakedFont, findNearestWeight, registerFont, MeshWriter } from 'meshwriter-cudu';// Available weights from your bake step
const availableWeights = [350, 400, 425, 450, 500];
// Find nearest to user's preferred weight
const userWeight = 415; // e.g., from user profile
const nearest = findNearestWeight(userWeight, availableWeights);
// Load just the one weight file (~45KB)
const fontSpec = await loadBakedFont(
/fonts/baked/atkinson-hyperlegible-next-${nearest}.json);
registerFont('Atkinson', fontSpec);// Create text
const Writer = await MeshWriter.createAsync(scene);
const text = new Writer("Hello!", {
'font-family': 'Atkinson',
'letter-height': 50
});
`$3
| Option | Description | Default |
|--------|-------------|---------|
|
--font=PATH | Variable font file (.ttf) | ./fonts/variable/atkinson-hyperlegible-next-variable.ttf |
| --weights=N,N,N | Comma-separated weights | 200,250,300,...,800 (13 weights) |
| --charset=CHARS | Characters to include | ASCII 32-126 (95 chars) |
| --output=DIR | Output directory | ./fonts/baked |$3
`javascript
import { loadBakedFontsFromManifest, getBakedFontManifest } from 'meshwriter-cudu';// Get available weights without loading fonts
const manifest = await getBakedFontManifest('/fonts/baked/manifest.json');
console.log(manifest.weights); // [200, 250, 300, ...]
// Load specific weights
const fonts = await loadBakedFontsFromManifest('/fonts/baked/manifest.json', [400, 450]);
const fontSpec = fonts.get(400);
`Advanced Features
$3
WCAG 2.1 compliant color utilities for accessible text:
`javascript
import { deriveEdgeColors, adjustForContrast, CONTRAST_LEVELS } from 'meshwriter-cudu';// Auto-generate high-contrast edge colors
const colors = deriveEdgeColors('#FF6600', CONTRAST_LEVELS.AA_NORMAL);
// { diffuse: '...', ambient: '...', emissive: '...' }
// Adjust existing colors to meet contrast requirements
const adjusted = adjustForContrast(
{ emissive: '#FF6600', diffuse: '#333333' },
{ targetContrast: 4.5 }
);
`$3
For bundled environments where globals may be tree-shaken:
`javascript
import { MeshWriter, registerFont } from 'meshwriter-cudu';
import { InitializeCSG2Async, IsCSG2Ready } from "@babylonjs/core/Meshes/csg2";
import helvetica from 'meshwriter-cudu/fonts/helvetica';registerFont('Helvetica', helvetica);
// Configure custom CSG handling
MeshWriter.setCSGInitializer(() => InitializeCSG2Async());
MeshWriter.setCSGReadyCheck(() => IsCSG2Ready());
const Writer = await MeshWriter.createAsync(scene, { scale: 1 });
`$3
By default, Babylon.js fog doesn't affect emissive materials. MeshWriter includes a material plugin to fix this:
`javascript
const text = new Writer("Foggy Text", {
'font-family': 'Helvetica',
'letter-height': 50,
'emissive-only': true, // Self-lit text
'fog-enabled': true // Respects scene fog
});
`UMD Bundle (Script Tag)
For non-bundler environments:
`html
`Building from Source
`bash
Install dependencies
npm installBuild all formats (ESM, CJS, UMD)
npm run buildBuild and watch
npm run devBake variable font weights
npm run bake:fontsType check
npm run typecheck
`$3
-
dist/meshwriter.esm.js - ES modules (tree-shakeable)
- dist/meshwriter.cjs.js - CommonJS (Node.js)
- dist/meshwriter.umd.js - UMD (script tags)
- dist/meshwriter.min.js` - Minified UMDThis project is a fork of meshwriter by Brian T Button. The original library pioneered 3D text rendering in Babylon.js and remains the foundation of this work.
What this fork adds:
- ES module support with tree-shaking
- Babylon.js 8+ compatibility with async CSG2 initialization
- Variable font weight support via build-time baking
- Dyslexia-friendly fonts (Atkinson Hyperlegible Next) with kerning
- TypeScript declarations
- WCAG color contrast utilities
- Fog plugin for emissive text
- Improved hole rendering for characters like 8, B, etc.
Thank you to Brian T Button for creating and maintaining the original meshwriter library.
MIT - See LICENSE file for details.