three-text 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -9
- package/dist/index.cjs +30 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30 -5
- package/dist/index.min.cjs +396 -394
- package/dist/index.min.js +394 -392
- package/dist/index.umd.js +30 -5
- package/dist/index.umd.min.js +544 -542
- package/dist/p5/index.cjs +3 -0
- package/dist/p5/index.js +3 -0
- package/dist/three/index.cjs +1 -0
- package/dist/three/index.d.ts +1 -0
- package/dist/three/index.js +1 -0
- package/dist/three/react.cjs +1 -0
- package/dist/three/react.d.ts +2 -0
- package/dist/three/react.js +1 -0
- package/dist/types/core/Text.d.ts +1 -0
- package/dist/types/core/font/WoffConverter.d.ts +4 -0
- package/dist/types/three/index.d.ts +1 -0
- package/dist/types/three/react.d.ts +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ High fidelity 3D mesh font geometry and text layout engine for the web
|
|
|
15
15
|
> [!CAUTION]
|
|
16
16
|
> three-text is an alpha release and the API may break rapidly. This warning will likely last until the end of March 2026. If API stability is important to you, consider pinning your version. Community feedback is encouraged; please open an issue if you have any suggestions or feedback, thank you
|
|
17
17
|
|
|
18
|
-
**three-text** is a 3D mesh font geometry and text layout library for the web. It supports TTF, OTF, and
|
|
18
|
+
**three-text** is a 3D mesh font geometry and text layout library for the web. It supports TTF, OTF, WOFF, and WOFF2 font files. For layout, it uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines and supports CJK and RTL scripts. three-text caches the geometries it generates for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate, and can be animated by re-drawing each frame with new coordinates
|
|
19
19
|
|
|
20
20
|
The library has a framework-agnostic core that returns raw vertex data, with lightweight adapters for [Three.js](https://threejs.org), [React Three Fiber](https://docs.pmnd.rs/react-three-fiber), [p5.js](https://p5js.org), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
21
21
|
|
|
@@ -63,14 +63,15 @@ npm install three
|
|
|
63
63
|
|
|
64
64
|
three-text has a framework-agnostic core that processes fonts and generates geometry data. Lightweight adapters convert this data to framework-specific formats:
|
|
65
65
|
|
|
66
|
-
- **`three-text`** -
|
|
67
|
-
- **`three-text/three`** -
|
|
66
|
+
- **`three-text`** - Three.js adapter (default export, returns BufferGeometry)
|
|
67
|
+
- **`three-text/three`** - Same as above (explicit alias)
|
|
68
68
|
- **`three-text/three/react`** - React Three Fiber component
|
|
69
|
+
- **`three-text/core`** - Framework-agnostic core (returns raw arrays)
|
|
69
70
|
- **`three-text/webgl`** - WebGL buffer utility
|
|
70
71
|
- **`three-text/webgpu`** - WebGPU buffer utility
|
|
71
72
|
- **`three-text/p5`** - p5.js adapter
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
Most users will just `import { Text } from 'three-text'` for Three.js projects
|
|
74
75
|
|
|
75
76
|
### Basic Usage
|
|
76
77
|
|
|
@@ -78,13 +79,14 @@ Choose the import that matches your stack. Most users will use `three-text/three
|
|
|
78
79
|
|
|
79
80
|
```javascript
|
|
80
81
|
import { Text } from 'three-text/three';
|
|
82
|
+
import { decode } from 'woff2-decode'; // Optional
|
|
81
83
|
import * as THREE from 'three';
|
|
82
84
|
|
|
83
85
|
Text.setHarfBuzzPath('/hb/hb.wasm');
|
|
84
|
-
|
|
86
|
+
Text.enableWoff2(decode); // Enabling WOFF2 support adds ~45kb to the bundle
|
|
85
87
|
const result = await Text.create({
|
|
86
88
|
text: 'Hello World',
|
|
87
|
-
font: '/fonts/Font.
|
|
89
|
+
font: '/fonts/Font.woff2',
|
|
88
90
|
size: 72
|
|
89
91
|
});
|
|
90
92
|
|
|
@@ -116,13 +118,15 @@ function App() {
|
|
|
116
118
|
|
|
117
119
|
```javascript
|
|
118
120
|
import 'three-text/p5';
|
|
121
|
+
import { decode } from 'woff2-decode';
|
|
119
122
|
|
|
120
123
|
let font;
|
|
121
124
|
let textResult;
|
|
122
125
|
|
|
123
126
|
function preload() {
|
|
124
127
|
loadThreeTextShaper('/hb/hb.wasm');
|
|
125
|
-
|
|
128
|
+
enableThreeTextWoff2(decode); // Optional, adds ~45KB to bundle
|
|
129
|
+
font = loadThreeTextFont('/fonts/Font.woff2');
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
async function setup() {
|
|
@@ -730,7 +734,7 @@ Below are the most important configuration interfaces. For a complete list of al
|
|
|
730
734
|
```typescript
|
|
731
735
|
interface TextOptions {
|
|
732
736
|
text: string; // Text content to render
|
|
733
|
-
font: string | ArrayBuffer; // Font file path or buffer (TTF, OTF, or
|
|
737
|
+
font: string | ArrayBuffer; // Font file path or buffer (TTF, OTF, WOFF, or WOFF2)
|
|
734
738
|
size?: number; // Font size in scene units (default: 72)
|
|
735
739
|
depth?: number; // Extrusion depth (default: 0)
|
|
736
740
|
lineHeight?: number; // Line height multiplier (default: 1.0)
|
|
@@ -913,7 +917,9 @@ The library requires WebAssembly support for HarfBuzz text shaping:
|
|
|
913
917
|
- Safari 16.4+
|
|
914
918
|
- Edge 80+
|
|
915
919
|
|
|
916
|
-
WOFF fonts are automatically decompressed to TTF/OTF using the browser's native
|
|
920
|
+
WOFF fonts are automatically decompressed to TTF/OTF using the browser's native `DecompressionStream` API with zero bundle cost. For older browsers without `DecompressionStream`, use TTF or OTF fonts directly
|
|
921
|
+
|
|
922
|
+
**WOFF2 font support** is opt-in (see [Basic Usage](#basic-usage))
|
|
917
923
|
|
|
918
924
|
**ES modules** (recommended) are supported in:
|
|
919
925
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.4.
|
|
2
|
+
* three-text v0.4.7
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -1469,7 +1469,7 @@ class FontMetadataExtractor {
|
|
|
1469
1469
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
1470
1470
|
];
|
|
1471
1471
|
if (!validSignatures.includes(sfntVersion)) {
|
|
1472
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
1472
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
1473
1473
|
}
|
|
1474
1474
|
const tableDirectory = parseTableDirectory(view);
|
|
1475
1475
|
const isCFF = tableDirectory.has(TABLE_TAG_CFF) || tableDirectory.has(TABLE_TAG_CFF2);
|
|
@@ -1658,7 +1658,7 @@ class FontMetadataExtractor {
|
|
|
1658
1658
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
1659
1659
|
];
|
|
1660
1660
|
if (!validSignatures.includes(sfntVersion)) {
|
|
1661
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
1661
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
1662
1662
|
}
|
|
1663
1663
|
const tableDirectory = parseTableDirectory(view);
|
|
1664
1664
|
const nameTableOffset = tableDirectory.get(TABLE_TAG_NAME)?.offset ?? 0;
|
|
@@ -1892,6 +1892,10 @@ class FontMetadataExtractor {
|
|
|
1892
1892
|
}
|
|
1893
1893
|
}
|
|
1894
1894
|
|
|
1895
|
+
let woff2Decoder = null;
|
|
1896
|
+
function setWoff2Decoder(decoder) {
|
|
1897
|
+
woff2Decoder = decoder;
|
|
1898
|
+
}
|
|
1895
1899
|
// Uses DecompressionStream to decompress WOFF (WOFF is just zlib compressed TTF/OTF so we can use deflate)
|
|
1896
1900
|
class WoffConverter {
|
|
1897
1901
|
static detectFormat(buffer) {
|
|
@@ -1993,6 +1997,21 @@ class WoffConverter {
|
|
|
1993
1997
|
logger.log('WOFF font decompressed successfully');
|
|
1994
1998
|
return sfntData.buffer.slice(0, sfntOffset);
|
|
1995
1999
|
}
|
|
2000
|
+
static async decompressWoff2(woff2Buffer) {
|
|
2001
|
+
const view = new DataView(woff2Buffer);
|
|
2002
|
+
const signature = view.getUint32(0);
|
|
2003
|
+
if (signature !== FONT_SIGNATURE_WOFF2) {
|
|
2004
|
+
throw new Error('Not a valid WOFF2 font');
|
|
2005
|
+
}
|
|
2006
|
+
if (!woff2Decoder) {
|
|
2007
|
+
throw new Error('WOFF2 fonts require enabling the decoder. Add to your code:\n' +
|
|
2008
|
+
" import { decode } from 'woff2-decode';\n" +
|
|
2009
|
+
' Text.enableWoff2(decode);');
|
|
2010
|
+
}
|
|
2011
|
+
const decoded = await woff2Decoder(woff2Buffer);
|
|
2012
|
+
logger.log('WOFF2 font decompressed successfully');
|
|
2013
|
+
return decoded.buffer;
|
|
2014
|
+
}
|
|
1996
2015
|
static async decompressZlib(compressedData) {
|
|
1997
2016
|
const stream = new ReadableStream({
|
|
1998
2017
|
start(controller) {
|
|
@@ -2023,7 +2042,8 @@ class FontLoader {
|
|
|
2023
2042
|
fontBuffer = await WoffConverter.decompressWoff(fontBuffer);
|
|
2024
2043
|
}
|
|
2025
2044
|
else if (format === 'woff2') {
|
|
2026
|
-
|
|
2045
|
+
logger.log('WOFF2 font detected, decompressing...');
|
|
2046
|
+
fontBuffer = await WoffConverter.decompressWoff2(fontBuffer);
|
|
2027
2047
|
}
|
|
2028
2048
|
const view = new DataView(fontBuffer);
|
|
2029
2049
|
const sfntVersion = view.getUint32(0);
|
|
@@ -2032,7 +2052,7 @@ class FontLoader {
|
|
|
2032
2052
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
2033
2053
|
];
|
|
2034
2054
|
if (!validSignatures.includes(sfntVersion)) {
|
|
2035
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
2055
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
2036
2056
|
}
|
|
2037
2057
|
const { hb, module } = await this.getHarfBuzzInstance();
|
|
2038
2058
|
try {
|
|
@@ -5582,6 +5602,11 @@ class Text {
|
|
|
5582
5602
|
static { this.fontCacheMemoryBytes = 0; }
|
|
5583
5603
|
static { this.maxFontCacheMemoryBytes = Infinity; }
|
|
5584
5604
|
static { this.fontIdCounter = 0; }
|
|
5605
|
+
// Enable WOFF2 font support (adds ~45KB to bundle if imported)
|
|
5606
|
+
// Usage: import { decode } from 'woff2-decode'; Text.enableWoff2(decode);
|
|
5607
|
+
static enableWoff2(decoder) {
|
|
5608
|
+
setWoff2Decoder(decoder);
|
|
5609
|
+
}
|
|
5585
5610
|
// Stringify with sorted keys for cache stability
|
|
5586
5611
|
static stableStringify(obj) {
|
|
5587
5612
|
const keys = Object.keys(obj).sort();
|
package/dist/index.d.ts
CHANGED
|
@@ -352,6 +352,7 @@ declare class Text {
|
|
|
352
352
|
private static fontCacheMemoryBytes;
|
|
353
353
|
private static maxFontCacheMemoryBytes;
|
|
354
354
|
private static fontIdCounter;
|
|
355
|
+
static enableWoff2(decoder: (data: ArrayBuffer | Uint8Array) => Uint8Array | Promise<Uint8Array>): void;
|
|
355
356
|
private static stableStringify;
|
|
356
357
|
private fontLoader;
|
|
357
358
|
private loadedFont?;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.4.
|
|
2
|
+
* three-text v0.4.7
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -1466,7 +1466,7 @@ class FontMetadataExtractor {
|
|
|
1466
1466
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
1467
1467
|
];
|
|
1468
1468
|
if (!validSignatures.includes(sfntVersion)) {
|
|
1469
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
1469
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
1470
1470
|
}
|
|
1471
1471
|
const tableDirectory = parseTableDirectory(view);
|
|
1472
1472
|
const isCFF = tableDirectory.has(TABLE_TAG_CFF) || tableDirectory.has(TABLE_TAG_CFF2);
|
|
@@ -1655,7 +1655,7 @@ class FontMetadataExtractor {
|
|
|
1655
1655
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
1656
1656
|
];
|
|
1657
1657
|
if (!validSignatures.includes(sfntVersion)) {
|
|
1658
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
1658
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
1659
1659
|
}
|
|
1660
1660
|
const tableDirectory = parseTableDirectory(view);
|
|
1661
1661
|
const nameTableOffset = tableDirectory.get(TABLE_TAG_NAME)?.offset ?? 0;
|
|
@@ -1889,6 +1889,10 @@ class FontMetadataExtractor {
|
|
|
1889
1889
|
}
|
|
1890
1890
|
}
|
|
1891
1891
|
|
|
1892
|
+
let woff2Decoder = null;
|
|
1893
|
+
function setWoff2Decoder(decoder) {
|
|
1894
|
+
woff2Decoder = decoder;
|
|
1895
|
+
}
|
|
1892
1896
|
// Uses DecompressionStream to decompress WOFF (WOFF is just zlib compressed TTF/OTF so we can use deflate)
|
|
1893
1897
|
class WoffConverter {
|
|
1894
1898
|
static detectFormat(buffer) {
|
|
@@ -1990,6 +1994,21 @@ class WoffConverter {
|
|
|
1990
1994
|
logger.log('WOFF font decompressed successfully');
|
|
1991
1995
|
return sfntData.buffer.slice(0, sfntOffset);
|
|
1992
1996
|
}
|
|
1997
|
+
static async decompressWoff2(woff2Buffer) {
|
|
1998
|
+
const view = new DataView(woff2Buffer);
|
|
1999
|
+
const signature = view.getUint32(0);
|
|
2000
|
+
if (signature !== FONT_SIGNATURE_WOFF2) {
|
|
2001
|
+
throw new Error('Not a valid WOFF2 font');
|
|
2002
|
+
}
|
|
2003
|
+
if (!woff2Decoder) {
|
|
2004
|
+
throw new Error('WOFF2 fonts require enabling the decoder. Add to your code:\n' +
|
|
2005
|
+
" import { decode } from 'woff2-decode';\n" +
|
|
2006
|
+
' Text.enableWoff2(decode);');
|
|
2007
|
+
}
|
|
2008
|
+
const decoded = await woff2Decoder(woff2Buffer);
|
|
2009
|
+
logger.log('WOFF2 font decompressed successfully');
|
|
2010
|
+
return decoded.buffer;
|
|
2011
|
+
}
|
|
1993
2012
|
static async decompressZlib(compressedData) {
|
|
1994
2013
|
const stream = new ReadableStream({
|
|
1995
2014
|
start(controller) {
|
|
@@ -2020,7 +2039,8 @@ class FontLoader {
|
|
|
2020
2039
|
fontBuffer = await WoffConverter.decompressWoff(fontBuffer);
|
|
2021
2040
|
}
|
|
2022
2041
|
else if (format === 'woff2') {
|
|
2023
|
-
|
|
2042
|
+
logger.log('WOFF2 font detected, decompressing...');
|
|
2043
|
+
fontBuffer = await WoffConverter.decompressWoff2(fontBuffer);
|
|
2024
2044
|
}
|
|
2025
2045
|
const view = new DataView(fontBuffer);
|
|
2026
2046
|
const sfntVersion = view.getUint32(0);
|
|
@@ -2029,7 +2049,7 @@ class FontLoader {
|
|
|
2029
2049
|
FONT_SIGNATURE_OPEN_TYPE_CFF
|
|
2030
2050
|
];
|
|
2031
2051
|
if (!validSignatures.includes(sfntVersion)) {
|
|
2032
|
-
throw new Error(`Invalid font format. Expected TTF/OTF
|
|
2052
|
+
throw new Error(`Invalid font format. Expected TTF/OTF/WOFF/WOFF2, got signature: 0x${sfntVersion.toString(16)}`);
|
|
2033
2053
|
}
|
|
2034
2054
|
const { hb, module } = await this.getHarfBuzzInstance();
|
|
2035
2055
|
try {
|
|
@@ -5579,6 +5599,11 @@ class Text {
|
|
|
5579
5599
|
static { this.fontCacheMemoryBytes = 0; }
|
|
5580
5600
|
static { this.maxFontCacheMemoryBytes = Infinity; }
|
|
5581
5601
|
static { this.fontIdCounter = 0; }
|
|
5602
|
+
// Enable WOFF2 font support (adds ~45KB to bundle if imported)
|
|
5603
|
+
// Usage: import { decode } from 'woff2-decode'; Text.enableWoff2(decode);
|
|
5604
|
+
static enableWoff2(decoder) {
|
|
5605
|
+
setWoff2Decoder(decoder);
|
|
5606
|
+
}
|
|
5582
5607
|
// Stringify with sorted keys for cache stability
|
|
5583
5608
|
static stableStringify(obj) {
|
|
5584
5609
|
const keys = Object.keys(obj).sort();
|