UNPKG

unicode-shaper

Version:

Shape unicode text so that renderers like WebGL and WebGPU can properly display the glyphs.

150 lines (133 loc) 4.49 kB
import { DEFAULT_OPTIONS } from './shape/internal.js'; import wasmBase64 from './uShaper.wasm.js'; /** WASM Free Command */ type WasmFree = (ptr: number, len: number) => void; /** WASM Process Text Command */ type WasmProcessText = (ptr: number, len: number, options: number) => void; /** WASM Alloc Sentinel Command */ type WasmAllocSentinel = (size: number) => number; /** WASM Is Right-to-Left Command */ type WasmIsRTL = (unicode: number) => number; /** WASM Is Chinese-Japanese-Korean Command */ type WasmIsCJK = (unicode: number) => number; /** * WASM based text shaper */ export class WasmTextShaper { instance!: WebAssembly.Instance; wasmMemory?: Uint8Array; tmpString = ''; /** Construct the WASM instance */ constructor() { const mod = new WebAssembly.Module(base64ToArrayBuffer(wasmBase64)); this.instance = new WebAssembly.Instance(mod, { env: { /** * Set a unicode array * @param ptr - pointer * @param len - length */ setUnicodeArray: (ptr: number, len: number): void => { this.tmpString = ''; if (len === 0 || ptr < 0) return; const buf = this.#get(ptr, len); for (let i = 0; i < len; i++) this.tmpString += String.fromCharCode(buf[i]); }, }, }); } /** * Converts a string into a shaped string * @param str - input string * @param options - shaping options * @returns - shaped string */ shapeString(str: string, options = DEFAULT_OPTIONS): string { const processText = this.instance.exports.processText as WasmProcessText; const free = this.instance.exports.free as WasmFree; if (str.length === 0) return str; const len = str.length; // NOTE: putString allocates memory, but processText will free it for us const ptr = this.#putString(str); processText(ptr, len, options); free(ptr, len); return this.tmpString; } /** * Check if a character is a "right-to-left" unicode character * @param unicode - input unicode character * @returns - True if right-to-left */ isRTL(unicode: number): boolean { const isRTL = this.instance.exports.isRTL as WasmIsRTL; return isRTL(unicode) === 1; } /** * Check if a character is CJK (Chinese, Japanese, or Korean) * @param unicode - input unicode character * @returns - True if CJK */ isCJK(unicode: number): boolean { const isCJK = this.instance.exports.isCJK as WasmIsCJK; return isCJK(unicode) === 1; } /** * Converts a string into a unicode array inside the WASM memory. Returns the pointer * @param str - input string * @returns - pointer */ #putString(str: string): number { const len = str.length; const buf = new Uint16Array(len); const ptr = this.#allocUnicodeArray(len); for (let i = 0; i < len; i++) buf[i] = str.charCodeAt(i); const view = this.#getMemory(); view.subarray(ptr, ptr + len * 2).set(new Uint8Array(buf.buffer)); return ptr; } /** * Reads a unicode array from the WASM memory * @param ptr - pointer * @param len - length * @returns - unicode array */ #get(ptr: number, len: number): Uint16Array { const view = this.#getMemory(); const view16 = new Uint16Array(view.buffer, ptr, len); const copy = new Uint16Array(len); for (let i = 0; i < len; i++) copy[i] = view16[i]; return copy; } /** * Allocates a unicode array * @param size - size of array * @returns - pointer to array memory */ #allocUnicodeArray(size: number): number { const allocUnicodeArray = this.instance.exports.allocUnicodeArray as WasmAllocSentinel; return allocUnicodeArray(size); } /** * Returns the WASM memory. Rebuilds pointer if needed * @returns - WASM memory */ #getMemory(): Uint8Array { const memory = this.instance.exports.memory as WebAssembly.Memory; if (this.wasmMemory === undefined || this.wasmMemory.buffer !== memory.buffer) { this.wasmMemory = new Uint8Array(memory.buffer); } return this.wasmMemory; } } /** * polyfill to convert wasm base64 binary to ArrayBuffer * @param base64 - input base64 string * @returns - ArrayBuffer */ function base64ToArrayBuffer(base64: string): ArrayBuffer { const binaryString = atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i); return bytes.buffer as ArrayBuffer; }