@forgeax/engine-font 0.0.0-dev.8d955ade1c79
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 +202 -0
- package/README.md +98 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/font-importer.test.d.ts +2 -0
- package/dist/__tests__/font-importer.test.d.ts.map +1 -0
- package/dist/__tests__/font-local-artifacts.test.d.ts +2 -0
- package/dist/__tests__/font-local-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/font.unit.test.d.ts +2 -0
- package/dist/__tests__/font.unit.test.d.ts.map +1 -0
- package/dist/cli-font.d.ts +96 -0
- package/dist/cli-font.d.ts.map +1 -0
- package/dist/cli-font.mjs +339 -0
- package/dist/cli-font.mjs.map +1 -0
- package/dist/font-importer.d.ts +18 -0
- package/dist/font-importer.d.ts.map +1 -0
- package/dist/font-importer.mjs +469 -0
- package/dist/font-importer.mjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +54 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-msdf-worker.mjs +35 -0
- package/dist/node-msdf-worker.mjs.map +1 -0
- package/dist/node-worker-adapter.d.ts +17 -0
- package/dist/node-worker-adapter.d.ts.map +1 -0
- package/dist/runtime/font-decoder.d.ts +3 -0
- package/dist/runtime/font-decoder.d.ts.map +1 -0
- package/package.json +80 -0
- package/src/__tests__/font-importer.test.ts +24 -0
- package/src/__tests__/font-local-artifacts.test.ts +58 -0
- package/src/__tests__/font.unit.test.ts +394 -0
- package/src/cli-font.ts +472 -0
- package/src/font-importer.ts +205 -0
- package/src/index.ts +7 -0
- package/src/node-msdf-worker.mjs +38 -0
- package/src/node-worker-adapter.ts +41 -0
- package/src/runtime/font-decoder.ts +70 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
|
|
3
|
+
if (parentPort === null) {
|
|
4
|
+
throw new Error('the MSDF Node worker requires worker_threads.parentPort');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const listeners = new Map();
|
|
8
|
+
const endpoint = globalThis;
|
|
9
|
+
|
|
10
|
+
class NodeImageData {
|
|
11
|
+
constructor(data, width, height) {
|
|
12
|
+
this.data = data;
|
|
13
|
+
this.width = width;
|
|
14
|
+
this.height = height;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
endpoint.ImageData = NodeImageData;
|
|
19
|
+
endpoint.addEventListener = (type, listener) => {
|
|
20
|
+
if (type !== 'message') return;
|
|
21
|
+
const handler = (data) => listener({ data, origin: '*' });
|
|
22
|
+
listeners.set(listener, handler);
|
|
23
|
+
parentPort.on('message', handler);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
endpoint.removeEventListener = (type, listener) => {
|
|
27
|
+
if (type !== 'message') return;
|
|
28
|
+
const handler = listeners.get(listener);
|
|
29
|
+
if (handler === undefined) return;
|
|
30
|
+
listeners.delete(listener);
|
|
31
|
+
parentPort.off('message', handler);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
endpoint.postMessage = (message, transferList = []) => {
|
|
35
|
+
parentPort.postMessage(message, [...transferList]);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
await import('@zappar/msdf-generator/worker.js');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
|
|
3
|
+
interface MessageEventLike {
|
|
4
|
+
readonly data: unknown;
|
|
5
|
+
readonly origin: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type MessageListener = (event: MessageEventLike) => void;
|
|
9
|
+
|
|
10
|
+
/** Comlink's browser Worker-shaped endpoint backed by a Node worker thread. */
|
|
11
|
+
export class NodeWorkerAdapter {
|
|
12
|
+
private readonly worker: Worker;
|
|
13
|
+
private readonly listeners = new Map<MessageListener, (data: unknown) => void>();
|
|
14
|
+
|
|
15
|
+
public constructor(url: string | URL) {
|
|
16
|
+
this.worker = new Worker(url);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public postMessage(message: unknown, transferList: readonly ArrayBuffer[] = []): void {
|
|
20
|
+
this.worker.postMessage(message, [...transferList]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public addEventListener(type: string, listener: MessageListener): void {
|
|
24
|
+
if (type !== 'message') return;
|
|
25
|
+
const handler = (data: unknown) => listener({ data, origin: '*' });
|
|
26
|
+
this.listeners.set(listener, handler);
|
|
27
|
+
this.worker.on('message', handler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public removeEventListener(type: string, listener: MessageListener): void {
|
|
31
|
+
if (type !== 'message') return;
|
|
32
|
+
const handler = this.listeners.get(listener);
|
|
33
|
+
if (handler === undefined) return;
|
|
34
|
+
this.listeners.delete(listener);
|
|
35
|
+
this.worker.off('message', handler);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public terminate(): Promise<number> {
|
|
39
|
+
return this.worker.terminate();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import {
|
|
3
|
+
type AssetDecoderContribution,
|
|
4
|
+
type AssetKind,
|
|
5
|
+
err,
|
|
6
|
+
type FontAsset,
|
|
7
|
+
ok,
|
|
8
|
+
} from '@forgeax/engine-types';
|
|
9
|
+
|
|
10
|
+
function parseGuid(value: unknown): FontAsset['atlas'] | undefined {
|
|
11
|
+
if (value instanceof Uint8Array && value.length === 16) return value as FontAsset['atlas'];
|
|
12
|
+
if (
|
|
13
|
+
Array.isArray(value) &&
|
|
14
|
+
value.length === 16 &&
|
|
15
|
+
value.every((item) => Number.isInteger(item) && item >= 0 && item <= 255)
|
|
16
|
+
) {
|
|
17
|
+
return Uint8Array.from(value) as FontAsset['atlas'];
|
|
18
|
+
}
|
|
19
|
+
if (typeof value !== 'string') return undefined;
|
|
20
|
+
const parsed = AssetGuid.parse(value);
|
|
21
|
+
return parsed.ok ? parsed.value : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validCommon(value: unknown): value is FontAsset['common'] {
|
|
25
|
+
if (value === null || typeof value !== 'object') return false;
|
|
26
|
+
const common = value as Record<string, unknown>;
|
|
27
|
+
return ['lineHeight', 'base', 'distanceRange', 'pxRange', 'atlasWidth', 'atlasHeight'].every(
|
|
28
|
+
(key) => typeof common[key] === 'number' && Number.isFinite(common[key]),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const fontContribution: AssetDecoderContribution<FontAsset, 'font'> = {
|
|
33
|
+
kind: { kind: 'font' } as AssetKind<FontAsset, 'font'>,
|
|
34
|
+
consumer: 'GlyphTextLayout',
|
|
35
|
+
decoder: {
|
|
36
|
+
async decode({ envelope }) {
|
|
37
|
+
const payload = envelope.payload as unknown;
|
|
38
|
+
if (payload !== null && typeof payload === 'object') {
|
|
39
|
+
const source = payload as Record<string, unknown>;
|
|
40
|
+
const atlas = parseGuid(source.atlas ?? source.atlasGuid);
|
|
41
|
+
const sampler = parseGuid(source.sampler ?? source.samplerGuid);
|
|
42
|
+
if (
|
|
43
|
+
(source.kind === undefined || source.kind === 'font') &&
|
|
44
|
+
atlas !== undefined &&
|
|
45
|
+
sampler !== undefined &&
|
|
46
|
+
source.glyphs !== null &&
|
|
47
|
+
typeof source.glyphs === 'object' &&
|
|
48
|
+
validCommon(source.common)
|
|
49
|
+
) {
|
|
50
|
+
return ok({
|
|
51
|
+
kind: 'font',
|
|
52
|
+
atlas,
|
|
53
|
+
sampler,
|
|
54
|
+
glyphs: source.glyphs as FontAsset['glyphs'],
|
|
55
|
+
common: source.common,
|
|
56
|
+
...(source.notdef === undefined
|
|
57
|
+
? {}
|
|
58
|
+
: { notdef: source.notdef as NonNullable<FontAsset['notdef']> }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return err({
|
|
63
|
+
code: 'asset-package-invalid',
|
|
64
|
+
expected: 'a font payload with atlas and sampler references',
|
|
65
|
+
hint: 'recook the font atlas and publish its local sub-assets',
|
|
66
|
+
detail: { guid: envelope.guid, reason: 'font owner validation failed' },
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|