unicode-shaper
Version:
Shape unicode text so that renderers like WebGL and WebGPU can properly display the glyphs.
130 lines • 4.21 kB
JavaScript
import { DEFAULT_OPTIONS } from './shape/internal.js';
import wasmBase64 from './uShaper.wasm.js';
/**
* WASM based text shaper
*/
export class WasmTextShaper {
instance;
wasmMemory;
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, len) => {
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, options = DEFAULT_OPTIONS) {
const processText = this.instance.exports.processText;
const free = this.instance.exports.free;
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) {
const isRTL = this.instance.exports.isRTL;
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) {
const isCJK = this.instance.exports.isCJK;
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) {
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, len) {
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) {
const allocUnicodeArray = this.instance.exports.allocUnicodeArray;
return allocUnicodeArray(size);
}
/**
* Returns the WASM memory. Rebuilds pointer if needed
* @returns - WASM memory
*/
#getMemory() {
const memory = this.instance.exports.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) {
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;
}
//# sourceMappingURL=textShaperWasm.js.map