cn-font-split
Version:
划时代的字体切割工具,CJK与任何字符!支持 otf、ttf、woff2 字体多线程切割,完美地细颗粒度地进行包大小控制。A revolutionary font subetter that supports CJK and any characters! It enables multi-threaded subset of otf, ttf, and woff2 fonts, allowing for precise control over package size.
149 lines (141 loc) • 4.5 kB
text/typescript
import { api_interface } from '../gen/index';
import { IFs, Volume, createFsFromVolume } from 'memfs-browser';
import { WASI } from '@tybys/wasm-util';
import { FontSplitProps } from '../interface';
export * from '../interface.js';
export * from '../createAPI.js';
export class APIInterface {
constructor(
public key: string = Math.random().toString().replace('.', ''),
) {}
fs!: IFs;
async init(fs = createFsFromVolume(new Volume())) {
await fs.promises.mkdir('/tmp/fonts', { recursive: true });
await fs.promises.mkdir('/tmp/' + this.key);
this.fs = fs;
}
async setConfig(config: FontSplitProps | ArrayBuffer) {
const buffer =
config instanceof ArrayBuffer
? new Uint8Array(config)
: api_interface.InputTemplate.fromObject(
config as any,
).serialize();
await this.fs.promises.writeFile('/tmp/fonts/' + this.key, buffer);
}
async callback() {
const files = (await this.fs.promises.readdir(
'/tmp/' + this.key,
)) as string[];
return Promise.all(
files
.filter((i) => typeof i === 'string')
.map(async (file) => {
if (file) {
const path = '/tmp/' + this.key + '/' + file;
const data = (await this.fs.promises.readFile(
path,
)) as Uint8Array;
await this.fs.promises.unlink(path);
return {
name: file,
data,
};
}
}),
).finally(async () => {
await this.fs.promises.unlink('/tmp/fonts/' + this.key);
});
}
}
export async function fontSplit(
input: FontSplitProps | ArrayBuffer,
loadWasm: (
imports: any,
) => Promise<WebAssembly.WebAssemblyInstantiatedSource>,
options?: {
key?: string;
logger: (str: string, type: 'log' | 'error') => void;
fs?: IFs;
},
) {
const api = new APIInterface(options?.key);
await api.init();
await api.setConfig(input);
const { imports, wasi } = createWasi(api, options);
const wasm = await loadWasm(imports);
const { instance } = wasm;
console.time('wasm');
await wasi.start(instance);
console.timeEnd('wasm');
return api.callback();
}
export function createWasi(
api: APIInterface,
options:
| {
key?: string;
logger: (str: string, type: 'log' | 'error') => void;
fs?: IFs;
}
| undefined,
) {
const wasi = new WASI({
args: [api.key],
env: {
WASI_SDK_PATH: '/opt/wasi-sdk',
RUST_LOG: 'debug',
},
preopens: {
'/': '/',
},
// @ts-ignore
fs: api.fs,
print(text) {
options?.logger(text, 'log');
},
printErr(text) {
options?.logger(text, 'error');
},
});
const imports = {
wasi_snapshot_preview1: wasi.wasiImport,
env: {
pthread_mutex_init: () => {
console.log('Initializing mutex');
return 0; // 成功初始化
},
pthread_mutex_lock: () => {
console.log('Locking mutex');
return 0; // 成功锁定
},
pthread_mutex_unlock: () => {
console.log('Unlocking mutex');
return 0; // 成功解锁
},
pthread_mutex_destroy: () => {
console.log('Destroying mutex');
return 0; // 成功销毁
},
},
};
return { imports, wasi };
}
export class StaticWasm {
wasmBuffer: Promise<ArrayBuffer>;
url = '';
constructor(url: string | Uint8Array) {
if (typeof url === 'string') {
this.wasmBuffer = fetch(url).then((res) => res.arrayBuffer());
} else {
this.wasmBuffer = Promise.resolve(url.buffer as ArrayBuffer);
}
}
WasiHandle = async (imports: any) => {
return WebAssembly.instantiate(
new Uint8Array((await this.wasmBuffer).slice(0)),
// './target/wasm32-wasip1/release/wasm_edge.Oz.wasm',
imports as any,
);
};
}