three-text 0.5.0 → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import * as THREE from 'three';
3
- import { VectorTextResult, Text as Text$2 } from './index';
3
+ import { VectorTextOptions, VectorResult, Text as Text$2 } from './index';
4
4
 
5
5
  interface HyphenationTrieNode {
6
6
  patterns: number[] | null;
@@ -255,6 +255,8 @@ declare class Text$1 {
255
255
  private static patternCache;
256
256
  private static hbInitPromise;
257
257
  private static fontCache;
258
+ private static fontLoadPromises;
259
+ private static fontRefCounts;
258
260
  private static fontCacheMemoryBytes;
259
261
  private static maxFontCacheMemoryBytes;
260
262
  private static fontIdCounter;
@@ -263,6 +265,7 @@ declare class Text$1 {
263
265
  private fontLoader;
264
266
  private loadedFont?;
265
267
  private currentFontId;
268
+ private currentFontCacheKey?;
266
269
  private textShaper?;
267
270
  private textLayout?;
268
271
  private constructor();
@@ -270,6 +273,8 @@ declare class Text$1 {
270
273
  static setHarfBuzzBuffer(wasmBuffer: ArrayBuffer): void;
271
274
  static init(): Promise<HarfBuzzInstance>;
272
275
  static create(options: TextOptions): Promise<TextLayoutHandle>;
276
+ private static retainFont;
277
+ private static releaseFont;
273
278
  private static resolveFont;
274
279
  private static loadAndCacheFont;
275
280
  private static trackFontCacheAdd;
@@ -277,6 +282,7 @@ declare class Text$1 {
277
282
  private static enforceFontCacheMemoryLimit;
278
283
  private static generateFontContentHash;
279
284
  private setLoadedFont;
285
+ private releaseCurrentFont;
280
286
  private loadFont;
281
287
  private createLayout;
282
288
  private prepareHyphenation;
@@ -293,14 +299,14 @@ declare class Text$1 {
293
299
  destroy(): void;
294
300
  }
295
301
 
296
- interface TextProps extends Omit<TextOptions, 'text' | 'color'> {
302
+ interface TextProps extends Omit<VectorTextOptions, 'text' | 'color'> {
297
303
  children: string;
298
304
  font: string | ArrayBuffer;
299
305
  fillColor?: THREE.ColorRepresentation;
300
306
  position?: [number, number, number];
301
307
  rotation?: [number, number, number];
302
308
  scale?: [number, number, number];
303
- onLoad?: (result: VectorTextResult) => void;
309
+ onLoad?: (result: VectorResult) => void;
304
310
  onError?: (error: Error) => void;
305
311
  }
306
312
  declare const Text: react.ForwardRefExoticComponent<TextProps & react.RefAttributes<THREE.Group<THREE.Object3DEventMap>>> & {
@@ -1,8 +1,6 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx } from 'react/jsx-runtime';
2
2
  import { useRef, forwardRef, useState, useEffect } from 'react';
3
- import * as THREE from 'three';
4
3
  import { Text as Text$1 } from './index.js';
5
- import { createVectorMeshes } from './loopBlinnTSL.js';
6
4
 
7
5
  function deepEqual(a, b) {
8
6
  if (a === b)
@@ -45,59 +43,64 @@ function useDeepCompareMemo(value) {
45
43
  return ref.current;
46
44
  }
47
45
 
48
- const hasNodeMaterials = typeof THREE.MeshBasicNodeMaterial !== 'undefined';
49
46
  const TextInner = forwardRef(function TextInner(props, ref) {
50
- const { children, font, fillColor = '#ffffff', position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1], onLoad, onError, ...restOptions } = props;
47
+ const { children, font, fillColor = '#ffffff', positionNode, colorNode, position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1], onLoad, onError, ...restOptions } = props;
51
48
  const memoizedTextOptions = useDeepCompareMemo(restOptions);
52
- const [meshes, setMeshes] = useState(null);
49
+ const [group, setGroup] = useState(null);
53
50
  const [error, setError] = useState(null);
54
51
  const resultRef = useRef(null);
55
- const meshesRef = useRef(null);
52
+ const opRef = useRef(Promise.resolve(null));
53
+ const onLoadRef = useRef(onLoad);
54
+ const onErrorRef = useRef(onError);
55
+ onLoadRef.current = onLoad;
56
+ onErrorRef.current = onError;
56
57
  useEffect(() => {
57
58
  let cancelled = false;
58
- if (!hasNodeMaterials) {
59
- const err = new Error('three-text/vector/react requires MeshBasicNodeMaterial (Three.js r170+ with node materials / WebGPU build)');
60
- setError(err);
61
- if (onError)
62
- onError(err);
63
- else
64
- console.error(err.message);
65
- return;
66
- }
67
59
  async function setup() {
68
60
  try {
69
61
  setError(null);
70
- const result = await Text$1.create({
71
- text: children,
72
- font,
73
- ...memoizedTextOptions
62
+ const resultPromise = opRef.current.catch(() => null).then(() => {
63
+ if (cancelled)
64
+ return null;
65
+ return resultRef.current
66
+ ? resultRef.current.update({
67
+ text: children,
68
+ font,
69
+ color: fillColor,
70
+ positionNode,
71
+ colorNode,
72
+ ...memoizedTextOptions
73
+ })
74
+ : Text$1.create({
75
+ text: children,
76
+ font,
77
+ color: fillColor,
78
+ positionNode,
79
+ colorNode,
80
+ ...memoizedTextOptions
81
+ });
74
82
  });
83
+ opRef.current = resultPromise.catch(() => null);
84
+ const result = await resultPromise;
85
+ if (!result)
86
+ return;
75
87
  if (cancelled) {
76
88
  result.dispose();
77
89
  return;
78
90
  }
79
- resultRef.current?.dispose();
91
+ const prev = resultRef.current;
80
92
  resultRef.current = result;
81
- const bounds = result.geometryData.planeBounds;
82
- const cx = (bounds.min.x + bounds.max.x) * 0.5;
83
- const cy = (bounds.min.y + bounds.max.y) * 0.5;
84
- const lb = createVectorMeshes(result.geometryData, fillColor);
85
- lb.setOffset(-cx, -cy, 0);
86
- lb.interiorMesh.renderOrder = 0;
87
- lb.curveMesh.renderOrder = 1;
88
- lb.fillMesh.renderOrder = 2;
89
- meshesRef.current?.dispose();
90
- meshesRef.current = lb;
91
- setMeshes(lb);
92
- if (onLoad)
93
- onLoad(result);
93
+ setGroup(result.group);
94
+ if (onLoadRef.current)
95
+ onLoadRef.current(result);
96
+ requestAnimationFrame(() => prev?.dispose());
94
97
  }
95
98
  catch (err) {
96
99
  const e = err;
97
100
  if (!cancelled) {
98
101
  setError(e);
99
- if (onError)
100
- onError(e);
102
+ if (onErrorRef.current)
103
+ onErrorRef.current(e);
101
104
  else
102
105
  console.error('three-text/vector/react:', e);
103
106
  }
@@ -106,17 +109,18 @@ const TextInner = forwardRef(function TextInner(props, ref) {
106
109
  setup();
107
110
  return () => {
108
111
  cancelled = true;
109
- meshesRef.current?.dispose();
110
- meshesRef.current = null;
112
+ };
113
+ }, [children, font, memoizedTextOptions, fillColor, positionNode, colorNode]);
114
+ useEffect(() => {
115
+ return () => {
111
116
  resultRef.current?.dispose();
112
117
  resultRef.current = null;
113
- setMeshes(null);
114
118
  };
115
- }, [children, font, memoizedTextOptions, fillColor, onLoad, onError]);
116
- if (error || !meshes) {
119
+ }, []);
120
+ if (error || !group) {
117
121
  return null;
118
122
  }
119
- return (jsxs("group", { ref: ref, position: position, rotation: rotation, scale: scale, children: [jsx("primitive", { object: meshes.interiorMesh }), jsx("primitive", { object: meshes.curveMesh }), jsx("primitive", { object: meshes.fillMesh })] }));
123
+ return (jsx("group", { ref: ref, position: position, rotation: rotation, scale: scale, children: jsx("primitive", { object: group }) }));
120
124
  });
121
125
  const Text = Object.assign(TextInner, {
122
126
  setHarfBuzzPath: Text$1.setHarfBuzzPath,
@@ -185,7 +185,9 @@ void main() {
185
185
  gl.enable(gl.STENCIL_TEST);
186
186
  gl.stencilMask(0xff);
187
187
  gl.stencilFunc(gl.ALWAYS, 0, 0xff);
188
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.INVERT);
188
+ // Nonzero winding: front faces increment, back faces decrement
189
+ gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP);
190
+ gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP);
189
191
  gl.colorMask(false, false, false, false);
190
192
  if (geometryResources.interiorIndexCount > 0) {
191
193
  gl.useProgram(interiorProgram.program);
@@ -183,7 +183,9 @@ void main() {
183
183
  gl.enable(gl.STENCIL_TEST);
184
184
  gl.stencilMask(0xff);
185
185
  gl.stencilFunc(gl.ALWAYS, 0, 0xff);
186
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.INVERT);
186
+ // Nonzero winding: front faces increment, back faces decrement
187
+ gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP);
188
+ gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP);
187
189
  gl.colorMask(false, false, false, false);
188
190
  if (geometryResources.interiorIndexCount > 0) {
189
191
  gl.useProgram(interiorProgram.program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-text",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "3D mesh font geometry and text layout engine for the web",
5
5
  "main": "dist/three/index.cjs",
6
6
  "module": "dist/three/index.js",