diff --git a/app/services/harfbuzz.ts b/app/services/harfbuzz.ts new file mode 100644 index 0000000..10f8d1f --- /dev/null +++ b/app/services/harfbuzz.ts @@ -0,0 +1,28 @@ +import Service from '@ember/service'; +import hb from 'harfbuzzjs/hbjs'; +import type { HBInstance } from 'harfbuzzjs/hbjs'; + +export default class HarfbuzzService extends Service { + declare hb: HBInstance; + + loadWASMPromise: undefined | Promise = undefined; + + async loadWASM() { + if (!this.loadWASMPromise) { + this.loadWASMPromise = this._loadWASM(); + } + + await this.loadWASMPromise; + } + + async _loadWASM() { + const result = await WebAssembly.instantiateStreaming(fetch('/hb.wasm')); + this.hb = hb(result.instance); + } +} + +declare module '@ember/service' { + interface Registry { + harfbuzz: HarfbuzzService; + } +} diff --git a/public/hb.wasm b/public/hb.wasm new file mode 100644 index 0000000..4584e53 Binary files /dev/null and b/public/hb.wasm differ diff --git a/types/harfbuzzjs.d.ts b/types/harfbuzzjs.d.ts new file mode 100644 index 0000000..fa41ac8 --- /dev/null +++ b/types/harfbuzzjs.d.ts @@ -0,0 +1,68 @@ +declare module 'harfbuzzjs/hbjs' { + type Direction = 'ltr' | 'rtl' | 'ttb' | 'btt'; + type BufferFlag = + | 'BOT' + | 'EOT' + | 'PRESERVE_DEFAULT_IGNORABLES' + | 'REMOVE_DEFAULT_IGNORABLES' + | 'DO_NOT_INSERT_DOTTED_CIRCLE' + | 'PRODUCE_UNSAFE_TO_CONCAT'; + + type HBBlob = unknown; + + export interface HBFace { + upem: number; //units per em + reference_table(table: string): Uint8Array; + getAxisInfos(): Record; + collectUnicodes(): Uint32Array; + destroy(): void; + } + + export interface SVGPathSegment { + type: 'M' | 'L' | 'Q' | 'C' | 'Z'; + values: number[]; + } + + export type BufferContent = { + g: number; //The glyph ID + cl: number; //The cluster ID + ax: number; //Advance width (width to advance after this glyph is painted) + ay: number; //Advance height (height to advance after this glyph is painted) + dx: number; //X displacement (adjustment in X dimension when painting this glyph) + dy: number; //Y displacement (adjustment in Y dimension when painting this glyph) + flags: number; // Glyph flags like `HB_GLYPH_FLAG_UNSAFE_TO_BREAK` (0x1) + }[]; + + export interface HBFont { + glyphName(glyphId: number): string; + glyphToPath(glyphId: number): string; + glyphToJson(glyphId: number): SVGPathSegment[]; + setScale(xScale: number, yScale: number): void; + setVariations(variations: Record): void; + destroy(): void; + } + + interface HBBuffer { + addText(text: string): void; + guessSegmentProperties(): void; + setDirection(dir: Direction): void; + setFlags(flags: BufferFlag[]): void; + setLanguage(language: string): void; + setScript(script: string): void; + setClusterLevel(level: number): void; + json(): BufferContent; + destroy(): void; + } + + export interface HBInstance { + createBlob(buffer: ArrayBuffer): HBBlob; + createFace(blob: HBBlob, fontIndex: number): HBFace; + createFont(face: HBFace): HBFont; + createBuffer(): HBBuffer; + shape(font: HBFont, buffer: HBBuffer): void; + } + + const hb: (instance: WebAssembly.Instance) => HBInstance; + + export default hb; +}