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 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 WOFF 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
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`** - Core (returns raw arrays)
67
- - **`three-text/three`** - Three.js (returns BufferGeometry)
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
- Choose the import that matches your stack. Most users will use `three-text/three` or `three-text/p5`
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.woff',
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
- font = loadThreeTextFont('/fonts/Font.woff');
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 WOFF)
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 decompression with zero bundle cost. For older browsers, use TTF or OTF fonts directly
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.5
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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
- throw new Error('WOFF2 fonts are not yet supported. Please use WOFF or TTF/OTF format.');
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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.5
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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
- throw new Error('WOFF2 fonts are not yet supported. Please use WOFF or TTF/OTF format.');
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 (or WOFF), got signature: 0x${sfntVersion.toString(16)}`);
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();