three-text 0.4.4 → 0.4.6
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 +17 -6
- package/dist/index.cjs +68 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +68 -23
- package/dist/index.min.cjs +668 -659
- package/dist/index.min.js +644 -635
- package/dist/index.umd.js +68 -23
- package/dist/index.umd.min.js +611 -602
- 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/cache/GlyphContourCollector.d.ts +1 -1
- package/dist/types/core/font/WoffConverter.d.ts +4 -0
- package/dist/types/core/geometry/BoundaryClusterer.d.ts +1 -1
- package/dist/types/core/geometry/Polygonizer.d.ts +1 -1
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/three/index.d.ts +1 -0
- package/dist/types/three/react.d.ts +1 -0
- package/dist/types/utils/loadBinary.d.ts +1 -0
- package/package.json +4 -2
- /package/dist/types/{core → utils}/vectors.d.ts +0 -0
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
|
|
|
@@ -78,13 +78,14 @@ Choose the import that matches your stack. Most users will use `three-text/three
|
|
|
78
78
|
|
|
79
79
|
```javascript
|
|
80
80
|
import { Text } from 'three-text/three';
|
|
81
|
+
import { decode } from 'woff2-decode'; // Optional
|
|
81
82
|
import * as THREE from 'three';
|
|
82
83
|
|
|
83
84
|
Text.setHarfBuzzPath('/hb/hb.wasm');
|
|
84
|
-
|
|
85
|
+
Text.enableWoff2(decode); // Enabling WOFF2 support adds ~45kb to the bundle
|
|
85
86
|
const result = await Text.create({
|
|
86
87
|
text: 'Hello World',
|
|
87
|
-
font: '/fonts/Font.
|
|
88
|
+
font: '/fonts/Font.woff2',
|
|
88
89
|
size: 72
|
|
89
90
|
});
|
|
90
91
|
|
|
@@ -116,13 +117,15 @@ function App() {
|
|
|
116
117
|
|
|
117
118
|
```javascript
|
|
118
119
|
import 'three-text/p5';
|
|
120
|
+
import { decode } from 'woff2-decode';
|
|
119
121
|
|
|
120
122
|
let font;
|
|
121
123
|
let textResult;
|
|
122
124
|
|
|
123
125
|
function preload() {
|
|
124
126
|
loadThreeTextShaper('/hb/hb.wasm');
|
|
125
|
-
|
|
127
|
+
enableThreeTextWoff2(decode); // Optional, adds ~45KB to bundle
|
|
128
|
+
font = loadThreeTextFont('/fonts/Font.woff2');
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
async function setup() {
|
|
@@ -195,6 +198,12 @@ self.onmessage = (e) => {
|
|
|
195
198
|
|
|
196
199
|
The library will prioritize the buffer if both a path and a buffer have been set
|
|
197
200
|
|
|
201
|
+
#### Platform-specific notes
|
|
202
|
+
|
|
203
|
+
**NW.js with CommonJS:** If using `require()` to load the CJS build in NW.js, use Option 2 (buffer-based loading). NW.js's [dual-context architecture](https://docs.nwjs.io/For%20Users/Advanced/JavaScript%20Contexts%20in%20NW.js/#separate-context-mode) causes path resolution issues in this specific scenario. ESM imports and bundled code work normally
|
|
204
|
+
|
|
205
|
+
**Electron with `file://` protocol:** If loading HTML directly from the filesystem (not via a dev server), use Option 2 (buffer-based loading) or enable `nodeIntegration` in your BrowserWindow
|
|
206
|
+
|
|
198
207
|
### Hyphenation patterns
|
|
199
208
|
|
|
200
209
|
**For ES Modules (recommended):** Import and register only the languages you need:
|
|
@@ -724,7 +733,7 @@ Below are the most important configuration interfaces. For a complete list of al
|
|
|
724
733
|
```typescript
|
|
725
734
|
interface TextOptions {
|
|
726
735
|
text: string; // Text content to render
|
|
727
|
-
font: string | ArrayBuffer; // Font file path or buffer (TTF, OTF, or
|
|
736
|
+
font: string | ArrayBuffer; // Font file path or buffer (TTF, OTF, WOFF, or WOFF2)
|
|
728
737
|
size?: number; // Font size in scene units (default: 72)
|
|
729
738
|
depth?: number; // Extrusion depth (default: 0)
|
|
730
739
|
lineHeight?: number; // Line height multiplier (default: 1.0)
|
|
@@ -907,7 +916,9 @@ The library requires WebAssembly support for HarfBuzz text shaping:
|
|
|
907
916
|
- Safari 16.4+
|
|
908
917
|
- Edge 80+
|
|
909
918
|
|
|
910
|
-
WOFF fonts are automatically decompressed to TTF/OTF using the browser's native
|
|
919
|
+
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
|
|
920
|
+
|
|
921
|
+
**WOFF2 font support** is opt-in (see [Basic Usage](#basic-usage))
|
|
911
922
|
|
|
912
923
|
**ES modules** (recommended) are supported in:
|
|
913
924
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.4.
|
|
2
|
+
* three-text v0.4.6
|
|
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 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 = 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 = 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 {
|
|
@@ -2212,7 +2232,6 @@ async function loadPattern(language, patternsPath) {
|
|
|
2212
2232
|
}
|
|
2213
2233
|
}
|
|
2214
2234
|
|
|
2215
|
-
// Bector and bounding box types for core
|
|
2216
2235
|
// 2D Vector
|
|
2217
2236
|
class Vec2 {
|
|
2218
2237
|
constructor(x = 0, y = 0) {
|
|
@@ -4811,6 +4830,41 @@ class TextShaper {
|
|
|
4811
4830
|
}
|
|
4812
4831
|
}
|
|
4813
4832
|
|
|
4833
|
+
// Fetch with fs fallback for Electron file:// and Node.js environments
|
|
4834
|
+
async function loadBinary(filePath) {
|
|
4835
|
+
try {
|
|
4836
|
+
const res = await fetch(filePath);
|
|
4837
|
+
if (!res.ok) {
|
|
4838
|
+
throw new Error(`HTTP ${res.status}`);
|
|
4839
|
+
}
|
|
4840
|
+
return await res.arrayBuffer();
|
|
4841
|
+
}
|
|
4842
|
+
catch (fetchError) {
|
|
4843
|
+
const req = globalThis.require;
|
|
4844
|
+
if (typeof req !== 'function') {
|
|
4845
|
+
throw new Error(`Failed to fetch ${filePath}: ${fetchError}`);
|
|
4846
|
+
}
|
|
4847
|
+
try {
|
|
4848
|
+
const fs = req('fs');
|
|
4849
|
+
const nodePath = req('path');
|
|
4850
|
+
// file:// URLs need path resolution relative to the HTML document
|
|
4851
|
+
let resolvedPath = filePath;
|
|
4852
|
+
if (typeof window !== 'undefined' &&
|
|
4853
|
+
window.location?.protocol === 'file:') {
|
|
4854
|
+
const dir = nodePath.dirname(window.location.pathname);
|
|
4855
|
+
resolvedPath = nodePath.join(dir, filePath);
|
|
4856
|
+
}
|
|
4857
|
+
const buffer = fs.readFileSync(resolvedPath);
|
|
4858
|
+
if (buffer instanceof ArrayBuffer)
|
|
4859
|
+
return buffer;
|
|
4860
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
4861
|
+
}
|
|
4862
|
+
catch (fsError) {
|
|
4863
|
+
throw new Error(`Failed to load ${filePath}: fetch failed (${fetchError}), fs.readFileSync failed (${fsError})`);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4814
4868
|
var hb = {exports: {}};
|
|
4815
4869
|
|
|
4816
4870
|
var fs = {};
|
|
@@ -5401,11 +5455,9 @@ try {
|
|
|
5401
5455
|
var hbjsExports = hbjs$2.exports;
|
|
5402
5456
|
var hbjs$1 = /*@__PURE__*/getDefaultExportFromCjs(hbjsExports);
|
|
5403
5457
|
|
|
5404
|
-
// These will be bundled by Rollup
|
|
5405
|
-
// @ts-expect-error - no declarations for harfbuzzjs/hb.js
|
|
5406
5458
|
let harfbuzzPromise = null;
|
|
5407
5459
|
let wasmPath = null;
|
|
5408
|
-
let wasmBuffer = null;
|
|
5460
|
+
let wasmBuffer = null;
|
|
5409
5461
|
const HarfBuzzLoader = {
|
|
5410
5462
|
setWasmPath(path) {
|
|
5411
5463
|
wasmPath = path;
|
|
@@ -5428,12 +5480,7 @@ const HarfBuzzLoader = {
|
|
|
5428
5480
|
moduleConfig.wasmBinary = wasmBuffer;
|
|
5429
5481
|
}
|
|
5430
5482
|
else if (wasmPath) {
|
|
5431
|
-
moduleConfig.
|
|
5432
|
-
if (path.endsWith('.wasm')) {
|
|
5433
|
-
return wasmPath;
|
|
5434
|
-
}
|
|
5435
|
-
return scriptDirectory + path;
|
|
5436
|
-
};
|
|
5483
|
+
moduleConfig.wasmBinary = await loadBinary(wasmPath);
|
|
5437
5484
|
}
|
|
5438
5485
|
else {
|
|
5439
5486
|
throw new Error('HarfBuzz WASM path or buffer must be set before initialization.');
|
|
@@ -5555,6 +5602,11 @@ class Text {
|
|
|
5555
5602
|
static { this.fontCacheMemoryBytes = 0; }
|
|
5556
5603
|
static { this.maxFontCacheMemoryBytes = Infinity; }
|
|
5557
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
|
+
}
|
|
5558
5610
|
// Stringify with sorted keys for cache stability
|
|
5559
5611
|
static stableStringify(obj) {
|
|
5560
5612
|
const keys = Object.keys(obj).sort();
|
|
@@ -5730,14 +5782,7 @@ class Text {
|
|
|
5730
5782
|
Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
|
|
5731
5783
|
}
|
|
5732
5784
|
await Text.hbInitPromise;
|
|
5733
|
-
const fontBuffer = typeof fontSrc === 'string'
|
|
5734
|
-
? await fetch(fontSrc).then((res) => {
|
|
5735
|
-
if (!res.ok) {
|
|
5736
|
-
throw new Error(`Failed to load font from ${fontSrc}: HTTP ${res.status} ${res.statusText}`);
|
|
5737
|
-
}
|
|
5738
|
-
return res.arrayBuffer();
|
|
5739
|
-
})
|
|
5740
|
-
: fontSrc;
|
|
5785
|
+
const fontBuffer = typeof fontSrc === 'string' ? await loadBinary(fontSrc) : fontSrc;
|
|
5741
5786
|
try {
|
|
5742
5787
|
if (this.loadedFont) {
|
|
5743
5788
|
this.destroy();
|
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): 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.6
|
|
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 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 = 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 = 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 {
|
|
@@ -2209,7 +2229,6 @@ async function loadPattern(language, patternsPath) {
|
|
|
2209
2229
|
}
|
|
2210
2230
|
}
|
|
2211
2231
|
|
|
2212
|
-
// Bector and bounding box types for core
|
|
2213
2232
|
// 2D Vector
|
|
2214
2233
|
class Vec2 {
|
|
2215
2234
|
constructor(x = 0, y = 0) {
|
|
@@ -4808,6 +4827,41 @@ class TextShaper {
|
|
|
4808
4827
|
}
|
|
4809
4828
|
}
|
|
4810
4829
|
|
|
4830
|
+
// Fetch with fs fallback for Electron file:// and Node.js environments
|
|
4831
|
+
async function loadBinary(filePath) {
|
|
4832
|
+
try {
|
|
4833
|
+
const res = await fetch(filePath);
|
|
4834
|
+
if (!res.ok) {
|
|
4835
|
+
throw new Error(`HTTP ${res.status}`);
|
|
4836
|
+
}
|
|
4837
|
+
return await res.arrayBuffer();
|
|
4838
|
+
}
|
|
4839
|
+
catch (fetchError) {
|
|
4840
|
+
const req = globalThis.require;
|
|
4841
|
+
if (typeof req !== 'function') {
|
|
4842
|
+
throw new Error(`Failed to fetch ${filePath}: ${fetchError}`);
|
|
4843
|
+
}
|
|
4844
|
+
try {
|
|
4845
|
+
const fs = req('fs');
|
|
4846
|
+
const nodePath = req('path');
|
|
4847
|
+
// file:// URLs need path resolution relative to the HTML document
|
|
4848
|
+
let resolvedPath = filePath;
|
|
4849
|
+
if (typeof window !== 'undefined' &&
|
|
4850
|
+
window.location?.protocol === 'file:') {
|
|
4851
|
+
const dir = nodePath.dirname(window.location.pathname);
|
|
4852
|
+
resolvedPath = nodePath.join(dir, filePath);
|
|
4853
|
+
}
|
|
4854
|
+
const buffer = fs.readFileSync(resolvedPath);
|
|
4855
|
+
if (buffer instanceof ArrayBuffer)
|
|
4856
|
+
return buffer;
|
|
4857
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
4858
|
+
}
|
|
4859
|
+
catch (fsError) {
|
|
4860
|
+
throw new Error(`Failed to load ${filePath}: fetch failed (${fetchError}), fs.readFileSync failed (${fsError})`);
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4811
4865
|
var hb = {exports: {}};
|
|
4812
4866
|
|
|
4813
4867
|
var fs = {};
|
|
@@ -5398,11 +5452,9 @@ try {
|
|
|
5398
5452
|
var hbjsExports = hbjs$2.exports;
|
|
5399
5453
|
var hbjs$1 = /*@__PURE__*/getDefaultExportFromCjs(hbjsExports);
|
|
5400
5454
|
|
|
5401
|
-
// These will be bundled by Rollup
|
|
5402
|
-
// @ts-expect-error - no declarations for harfbuzzjs/hb.js
|
|
5403
5455
|
let harfbuzzPromise = null;
|
|
5404
5456
|
let wasmPath = null;
|
|
5405
|
-
let wasmBuffer = null;
|
|
5457
|
+
let wasmBuffer = null;
|
|
5406
5458
|
const HarfBuzzLoader = {
|
|
5407
5459
|
setWasmPath(path) {
|
|
5408
5460
|
wasmPath = path;
|
|
@@ -5425,12 +5477,7 @@ const HarfBuzzLoader = {
|
|
|
5425
5477
|
moduleConfig.wasmBinary = wasmBuffer;
|
|
5426
5478
|
}
|
|
5427
5479
|
else if (wasmPath) {
|
|
5428
|
-
moduleConfig.
|
|
5429
|
-
if (path.endsWith('.wasm')) {
|
|
5430
|
-
return wasmPath;
|
|
5431
|
-
}
|
|
5432
|
-
return scriptDirectory + path;
|
|
5433
|
-
};
|
|
5480
|
+
moduleConfig.wasmBinary = await loadBinary(wasmPath);
|
|
5434
5481
|
}
|
|
5435
5482
|
else {
|
|
5436
5483
|
throw new Error('HarfBuzz WASM path or buffer must be set before initialization.');
|
|
@@ -5552,6 +5599,11 @@ class Text {
|
|
|
5552
5599
|
static { this.fontCacheMemoryBytes = 0; }
|
|
5553
5600
|
static { this.maxFontCacheMemoryBytes = Infinity; }
|
|
5554
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
|
+
}
|
|
5555
5607
|
// Stringify with sorted keys for cache stability
|
|
5556
5608
|
static stableStringify(obj) {
|
|
5557
5609
|
const keys = Object.keys(obj).sort();
|
|
@@ -5727,14 +5779,7 @@ class Text {
|
|
|
5727
5779
|
Text.hbInitPromise = HarfBuzzLoader.getHarfBuzz();
|
|
5728
5780
|
}
|
|
5729
5781
|
await Text.hbInitPromise;
|
|
5730
|
-
const fontBuffer = typeof fontSrc === 'string'
|
|
5731
|
-
? await fetch(fontSrc).then((res) => {
|
|
5732
|
-
if (!res.ok) {
|
|
5733
|
-
throw new Error(`Failed to load font from ${fontSrc}: HTTP ${res.status} ${res.statusText}`);
|
|
5734
|
-
}
|
|
5735
|
-
return res.arrayBuffer();
|
|
5736
|
-
})
|
|
5737
|
-
: fontSrc;
|
|
5782
|
+
const fontBuffer = typeof fontSrc === 'string' ? await loadBinary(fontSrc) : fontSrc;
|
|
5738
5783
|
try {
|
|
5739
5784
|
if (this.loadedFont) {
|
|
5740
5785
|
this.destroy();
|