three-text 0.2.1 → 0.2.2
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 +23 -22
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/p5/index.cjs +92 -13
- package/dist/p5/index.d.ts +8 -2
- package/dist/p5/index.js +92 -13
- package/dist/types/p5/index.d.ts +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ A high fidelity 3D font renderer and text layout engine for the web
|
|
|
17
17
|
|
|
18
18
|
**three-text** renders and formats text from TTF, OTF, and WOFF font files as 3D geometry. It uses [TeX](https://en.wikipedia.org/wiki/TeX)-based parameters for breaking text into paragraphs across multiple lines, and turns font outlines into 3D shapes on the fly, caching their geometries for low CPU overhead in languages with lots of repeating glyphs. Variable fonts are supported as static instances at a given axis coordinate
|
|
19
19
|
|
|
20
|
-
The library has a framework-agnostic core that returns raw vertex data, with lightweight adapters for Three.js, React Three Fiber, p5.js, WebGL, and WebGPU
|
|
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
|
|
|
22
22
|
Under the hood, three-text relies on [HarfBuzz](https://github.com/harfbuzz/harfbuzzjs) for text shaping, [Knuth-Plass](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) line breaking, [Liang](https://tug.org/docs/liang/liang-thesis.pdf) hyphenation, [libtess2](https://github.com/memononen/libtess2) (based on the [OpenGL Utility Library (GLU) tessellator](https://www.songho.ca/opengl/gl_tessellation.html) by Eric Veach) for removing overlaps and triangulation, bezier curve polygonization from Maxim Shemanarev's [Anti-Grain Geometry](https://web.archive.org/web/20060128212843/http://www.antigrain.com/research/adaptive_bezier/index.html), and [Visvalingam-Whyatt](https://hull-repository.worktribe.com/preview/376364/000870493786962263.pdf) [line simplification](https://bost.ocks.org/mike/simplify/).
|
|
23
23
|
|
|
@@ -25,12 +25,12 @@ Under the hood, three-text relies on [HarfBuzz](https://github.com/harfbuzz/harf
|
|
|
25
25
|
|
|
26
26
|
- [Overview](#overview)
|
|
27
27
|
- [Getting started](#getting-started)
|
|
28
|
-
- [Three.js](#threejs
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [Core
|
|
28
|
+
- [Three.js](#threejs)
|
|
29
|
+
- [p5.js](#p5js)
|
|
30
|
+
- [React Three Fiber](#react-three-fiber)
|
|
31
|
+
- [WebGL](#webgl)
|
|
32
|
+
- [WebGPU](#webgpu)
|
|
33
|
+
- [Core](#core)
|
|
34
34
|
- [Development and examples](#development-and-examples)
|
|
35
35
|
- [Architecture](#architecture)
|
|
36
36
|
- [Why three-text?](#why-three-text)
|
|
@@ -70,7 +70,7 @@ three-text has a framework-agnostic core that processes fonts and generates geom
|
|
|
70
70
|
- **`three-text/three/react`** - React Three Fiber component
|
|
71
71
|
- **`three-text/webgl`** - WebGL buffer utility
|
|
72
72
|
- **`three-text/webgpu`** - WebGPU buffer utility
|
|
73
|
-
- **`three-text/p5`** - p5.js
|
|
73
|
+
- **`three-text/p5`** - p5.js adapter
|
|
74
74
|
|
|
75
75
|
Choose the import that matches your stack. Most users will use `three-text/three` or `three-text/p5`
|
|
76
76
|
|
|
@@ -97,28 +97,29 @@ scene.add(mesh);
|
|
|
97
97
|
#### p5.js
|
|
98
98
|
|
|
99
99
|
```javascript
|
|
100
|
-
import
|
|
101
|
-
import { createP5Geometry } from 'three-text/p5';
|
|
100
|
+
import 'three-text/p5';
|
|
102
101
|
|
|
103
|
-
let
|
|
102
|
+
let font;
|
|
103
|
+
let textResult;
|
|
104
|
+
|
|
105
|
+
function preload() {
|
|
106
|
+
loadThreeTextShaper('/hb/hb.wasm');
|
|
107
|
+
font = loadThreeTextFont('/fonts/Font.woff');
|
|
108
|
+
}
|
|
104
109
|
|
|
105
110
|
async function setup() {
|
|
106
111
|
createCanvas(400, 400, WEBGL);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
font: '/fonts/Font.woff',
|
|
112
|
-
size: 72
|
|
112
|
+
textResult = await createThreeTextGeometry('Hello p5!', {
|
|
113
|
+
font: font,
|
|
114
|
+
size: 72,
|
|
115
|
+
depth: 30
|
|
113
116
|
});
|
|
114
|
-
|
|
115
|
-
textGeom = createP5Geometry(window, data);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function draw() {
|
|
119
120
|
background(200);
|
|
120
121
|
lights();
|
|
121
|
-
model(
|
|
122
|
+
if (textResult) model(textResult.geometry);
|
|
122
123
|
}
|
|
123
124
|
```
|
|
124
125
|
|
|
@@ -244,7 +245,7 @@ three-text/
|
|
|
244
245
|
│ │ └── ThreeText.tsx # React Three Fiber component
|
|
245
246
|
│ ├── webgl/ # WebGL buffer utility
|
|
246
247
|
│ ├── webgpu/ # WebGPU buffer utility
|
|
247
|
-
│ ├── p5/ # p5.js
|
|
248
|
+
│ ├── p5/ # p5.js adapter
|
|
248
249
|
│ ├── hyphenation/ # Language-specific hyphenation patterns
|
|
249
250
|
│ └── utils/ # Performance logging, data structures
|
|
250
251
|
├── examples/ # Demos for all adapters
|
|
@@ -961,7 +962,7 @@ The build generates multiple module formats for core and all adapters:
|
|
|
961
962
|
- `dist/three/react.js` - React component
|
|
962
963
|
- `dist/webgl/` - WebGL utility
|
|
963
964
|
- `dist/webgpu/` - WebGPU utility
|
|
964
|
-
- `dist/p5/` - p5.js
|
|
965
|
+
- `dist/p5/` - p5.js adapter
|
|
965
966
|
|
|
966
967
|
**Patterns:**
|
|
967
968
|
- `dist/patterns/` - Hyphenation patterns (ESM and UMD)
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/index.min.cjs
CHANGED
package/dist/index.min.js
CHANGED
package/dist/index.umd.js
CHANGED
package/dist/index.umd.min.js
CHANGED
package/dist/p5/index.cjs
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const P5Geometry = p5Instance.Geometry || window.p5?.Geometry;
|
|
8
|
-
const createVec = p5Instance.createVector || window.createVector;
|
|
9
|
-
if (!P5Geometry || !createVec) {
|
|
10
|
-
throw new Error('p5.js not found. Make sure p5.js is loaded before calling this function.');
|
|
11
|
-
}
|
|
12
|
-
const geom = new P5Geometry();
|
|
3
|
+
var Text = require('../index.cjs');
|
|
4
|
+
|
|
5
|
+
// p5.js adapter
|
|
6
|
+
function convertToP5Geometry(p5Instance, textGeometry) {
|
|
13
7
|
const { vertices, normals, indices } = textGeometry;
|
|
14
|
-
|
|
8
|
+
const P5GeometryClass = p5Instance.constructor?.Geometry ||
|
|
9
|
+
(typeof window !== 'undefined' && window.p5?.Geometry);
|
|
10
|
+
if (!P5GeometryClass) {
|
|
11
|
+
throw new Error('p5.Geometry not found. Ensure p5.js is loaded.');
|
|
12
|
+
}
|
|
13
|
+
const geom = new P5GeometryClass();
|
|
14
|
+
const createVec = (x, y, z) => {
|
|
15
|
+
if (typeof p5Instance.createVector === 'function') {
|
|
16
|
+
return p5Instance.createVector(x, y, z);
|
|
17
|
+
}
|
|
18
|
+
const globalCreateVector = (typeof window !== 'undefined' && window.createVector);
|
|
19
|
+
if (globalCreateVector) {
|
|
20
|
+
return globalCreateVector(x, y, z);
|
|
21
|
+
}
|
|
22
|
+
throw new Error('createVector not found');
|
|
23
|
+
};
|
|
24
|
+
// p5 uses +Y up, we use +Y down
|
|
15
25
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
16
26
|
geom.vertices.push(createVec(vertices[i], -vertices[i + 1], vertices[i + 2]));
|
|
17
27
|
}
|
|
18
|
-
// Convert normals (flip Y)
|
|
19
28
|
for (let i = 0; i < normals.length; i += 3) {
|
|
20
29
|
geom.vertexNormals.push(createVec(normals[i], -normals[i + 1], normals[i + 2]));
|
|
21
30
|
}
|
|
@@ -29,5 +38,75 @@ function createP5Geometry(p5Instance, textGeometry) {
|
|
|
29
38
|
}
|
|
30
39
|
return geom;
|
|
31
40
|
}
|
|
41
|
+
let shaperInitialized = false;
|
|
42
|
+
if (typeof window !== 'undefined' && window.p5) {
|
|
43
|
+
const p5 = window.p5;
|
|
44
|
+
p5.prototype.loadThreeTextShaper = function (wasmPath) {
|
|
45
|
+
if (shaperInitialized) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
Text.Text.setHarfBuzzPath(wasmPath);
|
|
49
|
+
shaperInitialized = true;
|
|
50
|
+
Text.Text.init()
|
|
51
|
+
.then(() => {
|
|
52
|
+
this._decrementPreload();
|
|
53
|
+
})
|
|
54
|
+
.catch((err) => {
|
|
55
|
+
console.error('Failed to load text shaper:', err);
|
|
56
|
+
this._decrementPreload();
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
p5.prototype.loadThreeTextFont = function (fontPath, fontVariations) {
|
|
60
|
+
const fontRef = {
|
|
61
|
+
buffer: null,
|
|
62
|
+
path: fontPath,
|
|
63
|
+
variations: fontVariations
|
|
64
|
+
};
|
|
65
|
+
fetch(fontPath)
|
|
66
|
+
.then(res => {
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(`Failed to load font: HTTP ${res.status}`);
|
|
69
|
+
}
|
|
70
|
+
return res.arrayBuffer();
|
|
71
|
+
})
|
|
72
|
+
.then(buffer => {
|
|
73
|
+
fontRef.buffer = buffer;
|
|
74
|
+
this._decrementPreload();
|
|
75
|
+
})
|
|
76
|
+
.catch((err) => {
|
|
77
|
+
console.error(`Failed to load font ${fontPath}:`, err);
|
|
78
|
+
this._decrementPreload();
|
|
79
|
+
});
|
|
80
|
+
return fontRef;
|
|
81
|
+
};
|
|
82
|
+
p5.prototype.createThreeTextGeometry = async function (text, options) {
|
|
83
|
+
if (!options.font || !options.font.buffer) {
|
|
84
|
+
console.error('Font not loaded. Use loadThreeTextFont() in preload().');
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const { font, ...coreOptions } = options;
|
|
88
|
+
try {
|
|
89
|
+
const result = await Text.Text.create({
|
|
90
|
+
text,
|
|
91
|
+
font: font.buffer,
|
|
92
|
+
fontVariations: font.variations,
|
|
93
|
+
...coreOptions
|
|
94
|
+
});
|
|
95
|
+
const p5Instance = this;
|
|
96
|
+
const geometry = convertToP5Geometry(p5Instance, result);
|
|
97
|
+
return {
|
|
98
|
+
geometry,
|
|
99
|
+
planeBounds: result.planeBounds,
|
|
100
|
+
glyphs: result.glyphs
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error('Failed to create text geometry:', err);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
p5.prototype.registerPreloadMethod('loadThreeTextShaper', p5.prototype);
|
|
109
|
+
p5.prototype.registerPreloadMethod('loadThreeTextFont', p5.prototype);
|
|
110
|
+
}
|
|
32
111
|
|
|
33
|
-
exports.createP5Geometry =
|
|
112
|
+
exports.createP5Geometry = convertToP5Geometry;
|
package/dist/p5/index.d.ts
CHANGED
|
@@ -13,7 +13,13 @@ interface P5Geometry {
|
|
|
13
13
|
interface P5Instance {
|
|
14
14
|
Geometry: new () => P5Geometry;
|
|
15
15
|
createVector(x: number, y: number, z: number): P5Vector;
|
|
16
|
+
_decrementPreload(): void;
|
|
16
17
|
}
|
|
17
|
-
declare
|
|
18
|
+
declare global {
|
|
19
|
+
interface Window {
|
|
20
|
+
p5?: any;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
declare function convertToP5Geometry(p5Instance: P5Instance, textGeometry: TextGeometryInfo): P5Geometry;
|
|
18
24
|
|
|
19
|
-
export { createP5Geometry };
|
|
25
|
+
export { convertToP5Geometry as createP5Geometry };
|
package/dist/p5/index.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const P5Geometry = p5Instance.Geometry || window.p5?.Geometry;
|
|
6
|
-
const createVec = p5Instance.createVector || window.createVector;
|
|
7
|
-
if (!P5Geometry || !createVec) {
|
|
8
|
-
throw new Error('p5.js not found. Make sure p5.js is loaded before calling this function.');
|
|
9
|
-
}
|
|
10
|
-
const geom = new P5Geometry();
|
|
1
|
+
import { Text } from '../index.js';
|
|
2
|
+
|
|
3
|
+
// p5.js adapter
|
|
4
|
+
function convertToP5Geometry(p5Instance, textGeometry) {
|
|
11
5
|
const { vertices, normals, indices } = textGeometry;
|
|
12
|
-
|
|
6
|
+
const P5GeometryClass = p5Instance.constructor?.Geometry ||
|
|
7
|
+
(typeof window !== 'undefined' && window.p5?.Geometry);
|
|
8
|
+
if (!P5GeometryClass) {
|
|
9
|
+
throw new Error('p5.Geometry not found. Ensure p5.js is loaded.');
|
|
10
|
+
}
|
|
11
|
+
const geom = new P5GeometryClass();
|
|
12
|
+
const createVec = (x, y, z) => {
|
|
13
|
+
if (typeof p5Instance.createVector === 'function') {
|
|
14
|
+
return p5Instance.createVector(x, y, z);
|
|
15
|
+
}
|
|
16
|
+
const globalCreateVector = (typeof window !== 'undefined' && window.createVector);
|
|
17
|
+
if (globalCreateVector) {
|
|
18
|
+
return globalCreateVector(x, y, z);
|
|
19
|
+
}
|
|
20
|
+
throw new Error('createVector not found');
|
|
21
|
+
};
|
|
22
|
+
// p5 uses +Y up, we use +Y down
|
|
13
23
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
14
24
|
geom.vertices.push(createVec(vertices[i], -vertices[i + 1], vertices[i + 2]));
|
|
15
25
|
}
|
|
16
|
-
// Convert normals (flip Y)
|
|
17
26
|
for (let i = 0; i < normals.length; i += 3) {
|
|
18
27
|
geom.vertexNormals.push(createVec(normals[i], -normals[i + 1], normals[i + 2]));
|
|
19
28
|
}
|
|
@@ -27,5 +36,75 @@ function createP5Geometry(p5Instance, textGeometry) {
|
|
|
27
36
|
}
|
|
28
37
|
return geom;
|
|
29
38
|
}
|
|
39
|
+
let shaperInitialized = false;
|
|
40
|
+
if (typeof window !== 'undefined' && window.p5) {
|
|
41
|
+
const p5 = window.p5;
|
|
42
|
+
p5.prototype.loadThreeTextShaper = function (wasmPath) {
|
|
43
|
+
if (shaperInitialized) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
Text.setHarfBuzzPath(wasmPath);
|
|
47
|
+
shaperInitialized = true;
|
|
48
|
+
Text.init()
|
|
49
|
+
.then(() => {
|
|
50
|
+
this._decrementPreload();
|
|
51
|
+
})
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
console.error('Failed to load text shaper:', err);
|
|
54
|
+
this._decrementPreload();
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
p5.prototype.loadThreeTextFont = function (fontPath, fontVariations) {
|
|
58
|
+
const fontRef = {
|
|
59
|
+
buffer: null,
|
|
60
|
+
path: fontPath,
|
|
61
|
+
variations: fontVariations
|
|
62
|
+
};
|
|
63
|
+
fetch(fontPath)
|
|
64
|
+
.then(res => {
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new Error(`Failed to load font: HTTP ${res.status}`);
|
|
67
|
+
}
|
|
68
|
+
return res.arrayBuffer();
|
|
69
|
+
})
|
|
70
|
+
.then(buffer => {
|
|
71
|
+
fontRef.buffer = buffer;
|
|
72
|
+
this._decrementPreload();
|
|
73
|
+
})
|
|
74
|
+
.catch((err) => {
|
|
75
|
+
console.error(`Failed to load font ${fontPath}:`, err);
|
|
76
|
+
this._decrementPreload();
|
|
77
|
+
});
|
|
78
|
+
return fontRef;
|
|
79
|
+
};
|
|
80
|
+
p5.prototype.createThreeTextGeometry = async function (text, options) {
|
|
81
|
+
if (!options.font || !options.font.buffer) {
|
|
82
|
+
console.error('Font not loaded. Use loadThreeTextFont() in preload().');
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const { font, ...coreOptions } = options;
|
|
86
|
+
try {
|
|
87
|
+
const result = await Text.create({
|
|
88
|
+
text,
|
|
89
|
+
font: font.buffer,
|
|
90
|
+
fontVariations: font.variations,
|
|
91
|
+
...coreOptions
|
|
92
|
+
});
|
|
93
|
+
const p5Instance = this;
|
|
94
|
+
const geometry = convertToP5Geometry(p5Instance, result);
|
|
95
|
+
return {
|
|
96
|
+
geometry,
|
|
97
|
+
planeBounds: result.planeBounds,
|
|
98
|
+
glyphs: result.glyphs
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error('Failed to create text geometry:', err);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
p5.prototype.registerPreloadMethod('loadThreeTextShaper', p5.prototype);
|
|
107
|
+
p5.prototype.registerPreloadMethod('loadThreeTextFont', p5.prototype);
|
|
108
|
+
}
|
|
30
109
|
|
|
31
|
-
export { createP5Geometry };
|
|
110
|
+
export { convertToP5Geometry as createP5Geometry };
|
package/dist/types/p5/index.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ interface P5Geometry {
|
|
|
12
12
|
interface P5Instance {
|
|
13
13
|
Geometry: new () => P5Geometry;
|
|
14
14
|
createVector(x: number, y: number, z: number): P5Vector;
|
|
15
|
+
_decrementPreload(): void;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
declare global {
|
|
18
|
+
interface Window {
|
|
19
|
+
p5?: any;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
declare function convertToP5Geometry(p5Instance: P5Instance, textGeometry: TextGeometryInfo): P5Geometry;
|
|
23
|
+
export { convertToP5Geometry as createP5Geometry };
|