woff2-encode-wasm 0.1.1
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/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/encoder.wasm +0 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +164 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kyosuke Nakamura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# woff2-encode-wasm
|
|
2
|
+
|
|
3
|
+
Encode TTF/OTF fonts to WOFF2 using the official [google/woff2](https://github.com/google/woff2) C++ library compiled to WebAssembly.
|
|
4
|
+
|
|
5
|
+
**Primary runtime target: Cloudflare Workers.** Also works in Node.js ≥ 18 and Deno.
|
|
6
|
+
|
|
7
|
+
## What this package does
|
|
8
|
+
|
|
9
|
+
- Encodes TTF or OTF font bytes into WOFF2 format.
|
|
10
|
+
- Uses the same encoder as the `woff2_compress` CLI from google/woff2.
|
|
11
|
+
- Runs entirely in-memory — no filesystem, no child processes, no native addons.
|
|
12
|
+
|
|
13
|
+
## What this package does NOT do
|
|
14
|
+
|
|
15
|
+
- WOFF2 **decoding** (decompression) is out of scope.
|
|
16
|
+
- This is not a general-purpose font conversion toolkit.
|
|
17
|
+
- It does not parse, subset, or modify font tables.
|
|
18
|
+
- It does not serve or render fonts.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install woff2-encode-wasm
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> **Note:** This package is ESM-only. It cannot be `require()`'d from CommonJS.
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { init, encode } from 'woff2-encode-wasm';
|
|
32
|
+
|
|
33
|
+
// Initialise the Wasm module (required once before encoding).
|
|
34
|
+
await init(wasmSource);
|
|
35
|
+
|
|
36
|
+
// Encode a font.
|
|
37
|
+
const woff2Bytes: Uint8Array = await encode(ttfBytes);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `init(source: WasmSource): Promise<void>`
|
|
41
|
+
|
|
42
|
+
Initialise the WebAssembly module. Must be called once before `encode()`.
|
|
43
|
+
|
|
44
|
+
`WasmSource` can be:
|
|
45
|
+
|
|
46
|
+
| Type | Use case |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `WebAssembly.Module` | Cloudflare Workers (import `.wasm` file) |
|
|
49
|
+
| `ArrayBuffer` / `Uint8Array` | Node.js / Deno (read `.wasm` bytes) |
|
|
50
|
+
| `Response` / `Promise<Response>` | `fetch()` based loading |
|
|
51
|
+
|
|
52
|
+
Calling `init()` again after successful initialisation is a no-op.
|
|
53
|
+
|
|
54
|
+
### `encode(input: Uint8Array): Promise<Uint8Array>`
|
|
55
|
+
|
|
56
|
+
Encode TTF/OTF bytes to WOFF2. Returns a fresh `Uint8Array` that the caller owns.
|
|
57
|
+
|
|
58
|
+
Throws if:
|
|
59
|
+
- Module is not initialised.
|
|
60
|
+
- Input is empty or not a `Uint8Array`.
|
|
61
|
+
- The font is corrupt or unsupported.
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Cloudflare Workers
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import wasmModule from 'woff2-encode-wasm/encoder.wasm';
|
|
69
|
+
import { init, encode } from 'woff2-encode-wasm';
|
|
70
|
+
|
|
71
|
+
// Initialise once (cached across requests in the same isolate).
|
|
72
|
+
const ready = init(wasmModule);
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
async fetch(request) {
|
|
76
|
+
await ready;
|
|
77
|
+
const input = new Uint8Array(await request.arrayBuffer());
|
|
78
|
+
const woff2 = await encode(input);
|
|
79
|
+
return new Response(woff2, {
|
|
80
|
+
headers: { 'Content-Type': 'font/woff2' },
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Node.js
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
import { readFileSync } from 'node:fs';
|
|
90
|
+
import { createRequire } from 'node:module';
|
|
91
|
+
import { init, encode } from 'woff2-encode-wasm';
|
|
92
|
+
|
|
93
|
+
const require = createRequire(import.meta.url);
|
|
94
|
+
const wasmPath = require.resolve('woff2-encode-wasm/encoder.wasm');
|
|
95
|
+
await init(readFileSync(wasmPath));
|
|
96
|
+
|
|
97
|
+
const ttf = new Uint8Array(readFileSync('input.ttf'));
|
|
98
|
+
const woff2 = await encode(ttf);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Deno
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { init, encode } from 'npm:woff2-encode-wasm';
|
|
105
|
+
import { createRequire } from 'node:module';
|
|
106
|
+
|
|
107
|
+
const require = createRequire(import.meta.url);
|
|
108
|
+
const wasmPath = require.resolve('woff2-encode-wasm/encoder.wasm');
|
|
109
|
+
await init(Deno.readFileSync(wasmPath));
|
|
110
|
+
|
|
111
|
+
const ttf = new Uint8Array(await Deno.readFile('input.ttf'));
|
|
112
|
+
const woff2 = await encode(ttf);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
deno run --allow-read --allow-env your_script.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Build from source
|
|
120
|
+
|
|
121
|
+
Prerequisites:
|
|
122
|
+
|
|
123
|
+
- [Emscripten SDK](https://emscripten.org/docs/getting_started/) (`emcc` on PATH) — for building the `.wasm`
|
|
124
|
+
- [Deno](https://deno.com/) — only needed for `npm test` (runs the Deno smoke test)
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run build # fetch deps + compile wasm + compile TS
|
|
128
|
+
npm test # run all tests (Node + Workers + Deno)
|
|
129
|
+
npm run test:node # Node.js tests only (no Deno required)
|
|
130
|
+
npm run test:workers # Cloudflare Workers tests only
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Individual steps:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm run fetch-deps # download google/woff2 v1.0.2 + brotli v1.1.0
|
|
137
|
+
npm run build:wasm # compile to dist/encoder.wasm (~750 KB)
|
|
138
|
+
npm run build:js # compile TS to dist/index.js + dist/index.d.ts
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> **Release note:** `npm pack` / `npm publish` runs `prepack`, which
|
|
142
|
+
> rebuilds JS/types via `tsc` so they are always in sync with source.
|
|
143
|
+
> The `.wasm` binary is **not** rebuilt automatically because `emcc` may
|
|
144
|
+
> not be available on the publishing machine. If you change
|
|
145
|
+
> `src/native/wrapper.cc` or the build script, run `npm run build:wasm`
|
|
146
|
+
> manually before publishing.
|
|
147
|
+
|
|
148
|
+
## Architecture
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
src/native/wrapper.cc Thin C ABI over woff2::ConvertTTFToWOFF2
|
|
152
|
+
src/index.ts ESM wrapper — init() + encode()
|
|
153
|
+
scripts/fetch-deps.sh Downloads pinned google/woff2 + brotli releases
|
|
154
|
+
scripts/build-wasm.sh Emscripten build: .cc/.c → standalone .wasm
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The `.wasm` is built with `STANDALONE_WASM=1` and has only two imports:
|
|
158
|
+
|
|
159
|
+
| Import | Purpose |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `env.emscripten_notify_memory_growth` | No-op callback on memory growth |
|
|
162
|
+
| `wasi_snapshot_preview1.proc_exit` | Abort (should never fire) |
|
|
163
|
+
|
|
164
|
+
This makes the module trivially instantiable in any environment without Emscripten's JS glue.
|
|
165
|
+
|
|
166
|
+
### C ABI
|
|
167
|
+
|
|
168
|
+
| Export | Signature |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `woff2_alloc` | `(size: u32) → ptr` |
|
|
171
|
+
| `woff2_free` | `(ptr) → void` |
|
|
172
|
+
| `woff2_encode` | `(input_ptr, input_len: u32) → i32` (0 = success) |
|
|
173
|
+
| `woff2_result_ptr` | `() → ptr` |
|
|
174
|
+
| `woff2_result_size` | `() → u32` |
|
|
175
|
+
| `woff2_result_free` | `() → void` |
|
|
176
|
+
|
|
177
|
+
### Memory ownership
|
|
178
|
+
|
|
179
|
+
1. JS allocates input buffer via `woff2_alloc`, copies font bytes in.
|
|
180
|
+
2. `woff2_encode` allocates the output internally.
|
|
181
|
+
3. JS reads the result via `woff2_result_ptr` / `woff2_result_size`, copies into a fresh `Uint8Array`.
|
|
182
|
+
4. JS frees the result with `woff2_result_free` and the input with `woff2_free`.
|
|
183
|
+
|
|
184
|
+
## Known limitations
|
|
185
|
+
|
|
186
|
+
- **Encoding only.** There is no `decode()` function and none is planned.
|
|
187
|
+
- **TTF / OTF only.** TrueType Collections (`.ttc`) are not supported by the upstream encoder.
|
|
188
|
+
- **Synchronous Wasm execution.** Very large fonts may block the event loop. For Workers, this is bounded by the CPU time limit.
|
|
189
|
+
- **~750 KB Wasm binary.** This is after `-O3 -flto`. Most of the size comes from the Brotli encoder tables.
|
|
190
|
+
|
|
191
|
+
## Versions
|
|
192
|
+
|
|
193
|
+
| Dependency | Version |
|
|
194
|
+
|---|---|
|
|
195
|
+
| google/woff2 | v1.0.2 |
|
|
196
|
+
| google/brotli | v1.1.0 |
|
|
197
|
+
| Emscripten | CI pinned to 5.0.5 (local builds may vary) |
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT — same as the upstream google/woff2 and google/brotli.
|
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* woff2-encode-wasm — Encode TTF/OTF fonts to WOFF2 using the official
|
|
3
|
+
* google/woff2 library compiled to WebAssembly.
|
|
4
|
+
*
|
|
5
|
+
* This module intentionally only encodes. WOFF2 decoding is out of scope.
|
|
6
|
+
*
|
|
7
|
+
* ## Instantiation strategy
|
|
8
|
+
*
|
|
9
|
+
* The standalone .wasm has two tiny imports that we stub:
|
|
10
|
+
* - env.emscripten_notify_memory_growth (no-op, called when memory grows)
|
|
11
|
+
* - wasi_snapshot_preview1.proc_exit (throws — should never fire)
|
|
12
|
+
*
|
|
13
|
+
* For Cloudflare Workers the caller passes a pre-compiled WebAssembly.Module
|
|
14
|
+
* (imported from the .wasm file by the bundler). For Node.js and Deno the
|
|
15
|
+
* caller can pass an ArrayBuffer or Uint8Array of the .wasm bytes.
|
|
16
|
+
*/
|
|
17
|
+
/** Source material accepted by {@link init}. */
|
|
18
|
+
export type WasmSource = WebAssembly.Module | BufferSource | Response | Promise<Response>;
|
|
19
|
+
/**
|
|
20
|
+
* Initialise the WebAssembly module.
|
|
21
|
+
*
|
|
22
|
+
* In **Cloudflare Workers**, pass the imported `.wasm` module:
|
|
23
|
+
* ```ts
|
|
24
|
+
* import wasmModule from './encoder.wasm';
|
|
25
|
+
* import { init, encode } from 'woff2-encode-wasm';
|
|
26
|
+
* await init(wasmModule);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* In **Node.js / Deno**, pass the raw `.wasm` bytes:
|
|
30
|
+
* ```ts
|
|
31
|
+
* await init(fs.readFileSync('encoder.wasm'));
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Calling `init()` again after initialisation has started is a no-op —
|
|
35
|
+
* the first call's source wins.
|
|
36
|
+
*/
|
|
37
|
+
export declare function init(source: WasmSource): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Encode a TTF or OTF font to WOFF2.
|
|
40
|
+
*
|
|
41
|
+
* @param input - Raw font bytes (TTF or OTF).
|
|
42
|
+
* @returns WOFF2-encoded bytes.
|
|
43
|
+
* @throws If the module is not initialised, input is empty, or encoding fails.
|
|
44
|
+
*/
|
|
45
|
+
export declare function encode(input: Uint8Array): Promise<Uint8Array>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* woff2-encode-wasm — Encode TTF/OTF fonts to WOFF2 using the official
|
|
3
|
+
* google/woff2 library compiled to WebAssembly.
|
|
4
|
+
*
|
|
5
|
+
* This module intentionally only encodes. WOFF2 decoding is out of scope.
|
|
6
|
+
*
|
|
7
|
+
* ## Instantiation strategy
|
|
8
|
+
*
|
|
9
|
+
* The standalone .wasm has two tiny imports that we stub:
|
|
10
|
+
* - env.emscripten_notify_memory_growth (no-op, called when memory grows)
|
|
11
|
+
* - wasi_snapshot_preview1.proc_exit (throws — should never fire)
|
|
12
|
+
*
|
|
13
|
+
* For Cloudflare Workers the caller passes a pre-compiled WebAssembly.Module
|
|
14
|
+
* (imported from the .wasm file by the bundler). For Node.js and Deno the
|
|
15
|
+
* caller can pass an ArrayBuffer or Uint8Array of the .wasm bytes.
|
|
16
|
+
*/
|
|
17
|
+
let exports = null;
|
|
18
|
+
let initPromise = null;
|
|
19
|
+
const importObject = {
|
|
20
|
+
env: {
|
|
21
|
+
// Called by Emscripten when ALLOW_MEMORY_GROWTH triggers a resize.
|
|
22
|
+
emscripten_notify_memory_growth(_memoryIndex) {
|
|
23
|
+
// No-op — we re-read memory.buffer on every encode() call.
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
wasi_snapshot_preview1: {
|
|
27
|
+
proc_exit(code) {
|
|
28
|
+
throw new Error(`woff2 wasm called proc_exit(${code})`);
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Initialise the WebAssembly module.
|
|
34
|
+
*
|
|
35
|
+
* In **Cloudflare Workers**, pass the imported `.wasm` module:
|
|
36
|
+
* ```ts
|
|
37
|
+
* import wasmModule from './encoder.wasm';
|
|
38
|
+
* import { init, encode } from 'woff2-encode-wasm';
|
|
39
|
+
* await init(wasmModule);
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* In **Node.js / Deno**, pass the raw `.wasm` bytes:
|
|
43
|
+
* ```ts
|
|
44
|
+
* await init(fs.readFileSync('encoder.wasm'));
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Calling `init()` again after initialisation has started is a no-op —
|
|
48
|
+
* the first call's source wins.
|
|
49
|
+
*/
|
|
50
|
+
export async function init(source) {
|
|
51
|
+
if (exports)
|
|
52
|
+
return;
|
|
53
|
+
if (initPromise)
|
|
54
|
+
return initPromise;
|
|
55
|
+
initPromise = (async () => {
|
|
56
|
+
let instance;
|
|
57
|
+
if (source instanceof WebAssembly.Module) {
|
|
58
|
+
// Cloudflare Workers path: already a compiled Module
|
|
59
|
+
instance = await WebAssembly.instantiate(source, importObject);
|
|
60
|
+
}
|
|
61
|
+
else if (source instanceof Response || source instanceof Promise) {
|
|
62
|
+
// Streaming compile from fetch Response.
|
|
63
|
+
// instantiateStreaming requires content-type: application/wasm.
|
|
64
|
+
// Fall back to arrayBuffer() when the MIME type is wrong or
|
|
65
|
+
// when instantiateStreaming is not available.
|
|
66
|
+
const response = source instanceof Promise ? await source : source;
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`woff2-encode-wasm: fetch failed with HTTP ${response.status}` +
|
|
69
|
+
(response.url ? ` (${response.url})` : '') + '.');
|
|
70
|
+
}
|
|
71
|
+
if (typeof WebAssembly.instantiateStreaming === 'function' &&
|
|
72
|
+
(response.headers.get('content-type') || '').trim().toLowerCase().startsWith('application/wasm')) {
|
|
73
|
+
const result = await WebAssembly.instantiateStreaming(response, importObject);
|
|
74
|
+
instance = result.instance;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const buf = await response.arrayBuffer();
|
|
78
|
+
const result = await WebAssembly.instantiate(buf, importObject);
|
|
79
|
+
instance = result.instance;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (source) {
|
|
83
|
+
// ArrayBuffer / Uint8Array path (Node.js, Deno)
|
|
84
|
+
const result = await WebAssembly.instantiate(source, importObject);
|
|
85
|
+
instance = result.instance;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
throw new Error('woff2-encode-wasm: init() requires a WasmSource argument. ' +
|
|
89
|
+
'Pass a WebAssembly.Module (Workers) or ArrayBuffer/Uint8Array (Node/Deno).');
|
|
90
|
+
}
|
|
91
|
+
const ex = instance.exports;
|
|
92
|
+
// STANDALONE_WASM reactors must call _initialize() once.
|
|
93
|
+
ex._initialize();
|
|
94
|
+
exports = ex;
|
|
95
|
+
})();
|
|
96
|
+
try {
|
|
97
|
+
await initPromise;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Allow retrying on failure.
|
|
101
|
+
initPromise = null;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const WOFF2_SIGNATURE = 0x774f4632; // 'wOF2'
|
|
106
|
+
/**
|
|
107
|
+
* Encode a TTF or OTF font to WOFF2.
|
|
108
|
+
*
|
|
109
|
+
* @param input - Raw font bytes (TTF or OTF).
|
|
110
|
+
* @returns WOFF2-encoded bytes.
|
|
111
|
+
* @throws If the module is not initialised, input is empty, or encoding fails.
|
|
112
|
+
*/
|
|
113
|
+
export async function encode(input) {
|
|
114
|
+
if (!exports) {
|
|
115
|
+
throw new Error('woff2-encode-wasm: module not initialised. Call init() first.');
|
|
116
|
+
}
|
|
117
|
+
if (!(input instanceof Uint8Array) || input.length === 0) {
|
|
118
|
+
throw new Error('woff2-encode-wasm: input must be a non-empty Uint8Array.');
|
|
119
|
+
}
|
|
120
|
+
const { memory, woff2_alloc, woff2_free, woff2_encode, woff2_result_ptr, woff2_result_size, woff2_result_free } = exports;
|
|
121
|
+
// 1. Allocate input buffer in Wasm memory and copy data in.
|
|
122
|
+
const inputPtr = woff2_alloc(input.length);
|
|
123
|
+
if (inputPtr === 0) {
|
|
124
|
+
throw new Error('woff2-encode-wasm: failed to allocate input buffer.');
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
new Uint8Array(memory.buffer, inputPtr, input.length).set(input);
|
|
128
|
+
// 2. Call the encoder.
|
|
129
|
+
const rc = woff2_encode(inputPtr, input.length);
|
|
130
|
+
if (rc !== 0) {
|
|
131
|
+
const messages = {
|
|
132
|
+
1: 'MaxWOFF2CompressedSize returned 0 — input is not a valid font.',
|
|
133
|
+
2: 'Failed to allocate output buffer.',
|
|
134
|
+
3: 'ConvertTTFToWOFF2 failed — font may be corrupt or unsupported.',
|
|
135
|
+
4: 'Null input pointer.',
|
|
136
|
+
};
|
|
137
|
+
throw new Error(`woff2-encode-wasm: encoding failed (code ${rc}). ${messages[rc] ?? ''}`);
|
|
138
|
+
}
|
|
139
|
+
// 3. Copy result out of Wasm memory into a fresh Uint8Array,
|
|
140
|
+
// then always free the Wasm-side result buffer.
|
|
141
|
+
try {
|
|
142
|
+
const resultPtr = woff2_result_ptr();
|
|
143
|
+
const resultSize = woff2_result_size();
|
|
144
|
+
// Re-read memory.buffer — it may have grown during encoding.
|
|
145
|
+
const result = new Uint8Array(memory.buffer, resultPtr, resultSize).slice();
|
|
146
|
+
// Sanity check: WOFF2 files start with 'wOF2'.
|
|
147
|
+
if (result.length >= 4) {
|
|
148
|
+
const sig = (result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3];
|
|
149
|
+
if (sig !== WOFF2_SIGNATURE) {
|
|
150
|
+
throw new Error('woff2-encode-wasm: output does not have wOF2 signature.');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
// 4. Free the Wasm-side result buffer even if the signature check throws.
|
|
157
|
+
woff2_result_free();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
// Always free the input buffer.
|
|
162
|
+
woff2_free(inputPtr);
|
|
163
|
+
}
|
|
164
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "woff2-encode-wasm",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Encode TTF/OTF fonts to WOFF2 using the official google/woff2 library compiled to WebAssembly",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./encoder.wasm": "./dist/encoder.wasm"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/index.js",
|
|
18
|
+
"dist/index.d.ts",
|
|
19
|
+
"dist/encoder.wasm"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"fetch-deps": "bash scripts/fetch-deps.sh",
|
|
23
|
+
"build:wasm": "bash scripts/build-wasm.sh",
|
|
24
|
+
"build:js": "tsc",
|
|
25
|
+
"build": "npm run fetch-deps && npm run build:wasm && npm run build:js",
|
|
26
|
+
"test": "npm run test:node && npm run test:workers && npm run test:deno",
|
|
27
|
+
"test:node": "vitest run --config vitest.config.ts",
|
|
28
|
+
"test:workers": "vitest run --config vitest.workers.config.ts",
|
|
29
|
+
"test:deno": "deno run --allow-read test/deno-smoke.ts",
|
|
30
|
+
"prepack": "npm run build:js && test -f dist/encoder.wasm || (echo 'ERROR: dist/encoder.wasm missing. Run npm run build:wasm first.' >&2 && exit 1)",
|
|
31
|
+
"clean": "rm -rf dist vendor/google-woff2 vendor/brotli"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"woff2",
|
|
35
|
+
"encode",
|
|
36
|
+
"wasm",
|
|
37
|
+
"webassembly",
|
|
38
|
+
"font",
|
|
39
|
+
"ttf",
|
|
40
|
+
"otf",
|
|
41
|
+
"cloudflare-workers"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/kyosuke/woff2-encode-wasm.git"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@cloudflare/vitest-pool-workers": "^0.14.1",
|
|
53
|
+
"typescript": "^6.0.2",
|
|
54
|
+
"vitest": "^4.1.2",
|
|
55
|
+
"wrangler": "^4.80.0"
|
|
56
|
+
}
|
|
57
|
+
}
|