UNPKG

harfbuzzjs

Version:

Minimal version of HarfBuzz for JavaScript use

1,421 lines 53 kB
import createHarfBuzz from "./harfbuzz.js"; //#region src/helpers.ts let Module; let exports; let freeFuncPtr; const utf8Decoder = new TextDecoder("utf8"); const utf8Encoder = new TextEncoder(); const registry = new FinalizationRegistry((cleanup) => { cleanup(); }); function track(obj, destroy) { const ptr = obj.ptr; registry.register(obj, () => destroy(ptr)); } /** * Initialize the HarfBuzz module. * @param module The Emscripten module instance created with {@link * createHarfBuzz}. */ function init(module) { Module = module; exports = Module.wasmExports; freeFuncPtr = Module.addFunction((ptr) => { exports.free(ptr); }, "vi"); } function hb_tag(s) { return (s.charCodeAt(0) & 255) << 24 | (s.charCodeAt(1) & 255) << 16 | (s.charCodeAt(2) & 255) << 8 | (s.charCodeAt(3) & 255) << 0; } function hb_untag(tag) { return [ String.fromCharCode(tag >> 24 & 255), String.fromCharCode(tag >> 16 & 255), String.fromCharCode(tag >> 8 & 255), String.fromCharCode(tag >> 0 & 255) ].join(""); } function utf8_ptr_to_string(ptr, length) { let end; if (length == void 0) end = Module.HEAPU8.indexOf(0, ptr); else end = ptr + length; return utf8Decoder.decode(Module.HEAPU8.subarray(ptr, end)); } function utf16_ptr_to_string(ptr, length) { const end = ptr / 2 + length; return String.fromCharCode(...Module.HEAPU16.subarray(ptr / 2, end)); } /** * Use when you know the input range should be ASCII. * Faster than encoding to UTF-8 */ function string_to_ascii_ptr(text) { const ptr = exports.malloc(text.length + 1); for (let i = 0; i < text.length; ++i) { const char = text.charCodeAt(i); if (char > 127) throw new Error("Expected ASCII text"); Module.HEAPU8[ptr + i] = char; } Module.HEAPU8[ptr + text.length] = 0; return { ptr, length: text.length, free: function() { exports.free(ptr); } }; } function string_to_utf8_ptr(text) { const ptr = exports.malloc(text.length); utf8Encoder.encodeInto(text, Module.HEAPU8.subarray(ptr, ptr + text.length)); return { ptr, length: text.length, free: function() { exports.free(ptr); } }; } function string_to_utf16_ptr(text) { const ptr = exports.malloc(text.length * 2); const words = Module.HEAPU16.subarray(ptr / 2, ptr / 2 + text.length); for (let i = 0; i < words.length; ++i) words[i] = text.charCodeAt(i); return { ptr, length: words.length, free: function() { exports.free(ptr); } }; } function language_to_string(language) { return utf8_ptr_to_string(exports.hb_language_to_string(language)); } function language_from_string(str) { const languageStr = string_to_ascii_ptr(str); const languagePtr = exports.hb_language_from_string(languageStr.ptr, -1); languageStr.free(); return languagePtr; } /** * Return the typed array of HarfBuzz set contents. * @param setPtr Pointer of set * @returns Typed array instance */ function typed_array_from_set(setPtr) { const setCount = exports.hb_set_get_population(setPtr); const arrayPtr = exports.malloc(setCount << 2); const arrayOffset = arrayPtr >> 2; exports.hb_set_next_many(setPtr, -1, arrayPtr, setCount); return Module.HEAPU32.subarray(arrayOffset, arrayOffset + setCount); } //#endregion //#region src/types.ts const GlyphFlag = { UNSAFE_TO_BREAK: 1, UNSAFE_TO_CONCAT: 2, SAFE_TO_INSERT_TATWEEL: 4, DEFINED: 7 }; //#endregion //#region src/blob.ts /** * An object representing a {@link https://harfbuzz.github.io/harfbuzz-hb-blob.html | HarfBuzz blob}. * A blob wraps a chunk of binary data, typically the contents of a font file. */ var Blob = class { /** * @param data Binary font data. */ constructor(data) { const blobPtr = exports.malloc(data.byteLength); Module.HEAPU8.set(new Uint8Array(data), blobPtr); this.ptr = exports.hb_blob_create(blobPtr, data.byteLength, 2, blobPtr, freeFuncPtr); track(this, exports.hb_blob_destroy); } }; //#endregion //#region src/face.ts const HB_OT_NAME_ID_INVALID = 65535; const GlyphClass = { UNCLASSIFIED: 0, BASE_GLYPH: 1, LIGATURE: 2, MARK: 3, COMPONENT: 4 }; /** * An object representing a {@link https://harfbuzz.github.io/harfbuzz-hb-face.html | HarfBuzz face}. * A face represents a single face in a binary font file and is the * foundation for creating Font objects used in text shaping. */ var Face = class { constructor(arg, index = 0) { if (typeof arg === "number") this.ptr = exports.hb_face_reference(arg); else this.ptr = exports.hb_face_create(arg.ptr, index); this.upem = exports.hb_face_get_upem(this.ptr); track(this, exports.hb_face_destroy); } /** * Return the binary contents of an OpenType table. * @param table Table name * @returns A Uint8Array of the table data, or undefined if the table is not found. */ referenceTable(table) { const blob = exports.hb_face_reference_table(this.ptr, hb_tag(table)); const length = exports.hb_blob_get_length(blob); if (!length) return; const blobptr = exports.hb_blob_get_data(blob, 0); return Module.HEAPU8.subarray(blobptr, blobptr + length); } /** * Return variation axis infos. * @returns A dictionary mapping axis tags to {min, default, max} values. */ getAxisInfos() { const sp = Module.stackSave(); const axis = Module.stackAlloc(2048); const c = Module.stackAlloc(4); Module.HEAPU32[c / 4] = 64; exports.hb_ot_var_get_axis_infos(this.ptr, 0, c, axis); const result = {}; Array.from({ length: Module.HEAPU32[c / 4] }).forEach((_, i) => { result[hb_untag(Module.HEAPU32[axis / 4 + i * 8 + 1])] = { min: Module.HEAPF32[axis / 4 + i * 8 + 4], default: Module.HEAPF32[axis / 4 + i * 8 + 5], max: Module.HEAPF32[axis / 4 + i * 8 + 6] }; }); Module.stackRestore(sp); return result; } /** * Return unicodes the face supports. * @returns A Uint32Array of supported Unicode code points. */ collectUnicodes() { const unicodeSetPtr = exports.hb_set_create(); exports.hb_face_collect_unicodes(this.ptr, unicodeSetPtr); const result = typed_array_from_set(unicodeSetPtr); exports.hb_set_destroy(unicodeSetPtr); return result; } /** * Return all scripts enumerated in the specified face's * GSUB table or GPOS table. * @param table The table to query, either "GSUB" or "GPOS". * @returns An array of 4-character script tag strings. */ getTableScriptTags(table) { const sp = Module.stackSave(); const tableTag = hb_tag(table); let startOffset = 0; let scriptCount = 128; const scriptCountPtr = Module.stackAlloc(4); const scriptTagsPtr = Module.stackAlloc(512); const tags = []; while (scriptCount == 128) { Module.HEAPU32[scriptCountPtr / 4] = scriptCount; exports.hb_ot_layout_table_get_script_tags(this.ptr, tableTag, startOffset, scriptCountPtr, scriptTagsPtr); scriptCount = Module.HEAPU32[scriptCountPtr / 4]; const scriptTags = Module.HEAPU32.subarray(scriptTagsPtr / 4, scriptTagsPtr / 4 + scriptCount); tags.push(...Array.from(scriptTags).map(hb_untag)); startOffset += scriptCount; } Module.stackRestore(sp); return tags; } /** * Return all features enumerated in the specified face's * GSUB table or GPOS table. * @param table The table to query, either "GSUB" or "GPOS". * @returns An array of 4-character feature tag strings. */ getTableFeatureTags(table) { const sp = Module.stackSave(); const tableTag = hb_tag(table); let startOffset = 0; let featureCount = 128; const featureCountPtr = Module.stackAlloc(4); const featureTagsPtr = Module.stackAlloc(512); const tags = []; while (featureCount == 128) { Module.HEAPU32[featureCountPtr / 4] = featureCount; exports.hb_ot_layout_table_get_feature_tags(this.ptr, tableTag, startOffset, featureCountPtr, featureTagsPtr); featureCount = Module.HEAPU32[featureCountPtr / 4]; const featureTags = Module.HEAPU32.subarray(featureTagsPtr / 4, featureTagsPtr / 4 + featureCount); tags.push(...Array.from(featureTags).map(hb_untag)); startOffset += featureCount; } Module.stackRestore(sp); return tags; } /** * Return language tags in the given face's GSUB or GPOS table, underneath * the specified script index. * @param table The table to query, either "GSUB" or "GPOS". * @param scriptIndex The index of the script to query. * @returns An array of 4-character language tag strings. */ getScriptLanguageTags(table, scriptIndex) { const sp = Module.stackSave(); const tableTag = hb_tag(table); let startOffset = 0; let languageCount = 128; const languageCountPtr = Module.stackAlloc(4); const languageTagsPtr = Module.stackAlloc(512); const tags = []; while (languageCount == 128) { Module.HEAPU32[languageCountPtr / 4] = languageCount; exports.hb_ot_layout_script_get_language_tags(this.ptr, tableTag, scriptIndex, startOffset, languageCountPtr, languageTagsPtr); languageCount = Module.HEAPU32[languageCountPtr / 4]; const languageTags = Module.HEAPU32.subarray(languageTagsPtr / 4, languageTagsPtr / 4 + languageCount); tags.push(...Array.from(languageTags).map(hb_untag)); startOffset += languageCount; } Module.stackRestore(sp); return tags; } /** * Return all features in the specified face's GSUB table or GPOS table, * underneath the specified script and language. * @param table The table to query, either "GSUB" or "GPOS". * @param scriptIndex The index of the script to query. * @param languageIndex The index of the language to query. * @returns An array of 4-character feature tag strings. */ getLanguageFeatureTags(table, scriptIndex, languageIndex) { const sp = Module.stackSave(); const tableTag = hb_tag(table); let startOffset = 0; let featureCount = 128; const featureCountPtr = Module.stackAlloc(4); const featureTagsPtr = Module.stackAlloc(512); const tags = []; while (featureCount == 128) { Module.HEAPU32[featureCountPtr / 4] = featureCount; exports.hb_ot_layout_language_get_feature_tags(this.ptr, tableTag, scriptIndex, languageIndex, startOffset, featureCountPtr, featureTagsPtr); featureCount = Module.HEAPU32[featureCountPtr / 4]; const featureTags = Module.HEAPU32.subarray(featureTagsPtr / 4, featureTagsPtr / 4 + featureCount); tags.push(...Array.from(featureTags).map(hb_untag)); startOffset += featureCount; } Module.stackRestore(sp); return tags; } /** * Fetches a list of all lookups enumerated for the specified feature, in * the specified face's GSUB table or GPOS table. * @param table The table to query, either "GSUB" or "GPOS". * @param featureIndex The index of the requested feature. * @returns An array of lookup indexes. */ getFeatureLookups(table, featureIndex) { const sp = Module.stackSave(); const tableTag = hb_tag(table); let startOffset = 0; let lookupCount = 128; const lookupCountPtr = Module.stackAlloc(4); const lookupIndexesPtr = Module.stackAlloc(512); const lookups = []; while (lookupCount == 128) { Module.HEAPU32[lookupCountPtr / 4] = lookupCount; exports.hb_ot_layout_feature_get_lookups(this.ptr, tableTag, featureIndex, startOffset, lookupCountPtr, lookupIndexesPtr); lookupCount = Module.HEAPU32[lookupCountPtr / 4]; const lookupIndexes = Module.HEAPU32.subarray(lookupIndexesPtr / 4, lookupIndexesPtr / 4 + lookupCount); lookups.push(...Array.from(lookupIndexes)); startOffset += lookupCount; } Module.stackRestore(sp); return lookups; } /** * Get the GDEF class of the requested glyph. * @param glyph The glyph to get the class of. * @returns The {@link GlyphClass} of the glyph. */ getGlyphClass(glyph) { return exports.hb_ot_layout_get_glyph_class(this.ptr, glyph); } /** * Return all names in the specified face's name table. * @returns An array of {nameId, language} entries. */ listNames() { const sp = Module.stackSave(); const numEntriesPtr = Module.stackAlloc(4); const entriesPtr = exports.hb_ot_name_list_names(this.ptr, numEntriesPtr); const numEntries = Module.HEAPU32[numEntriesPtr / 4]; const entries = []; for (let i = 0; i < numEntries; i++) entries.push({ nameId: Module.HEAPU32[entriesPtr / 4 + i * 3], language: language_to_string(Module.HEAPU32[entriesPtr / 4 + i * 3 + 2]) }); Module.stackRestore(sp); return entries; } /** * Get the name of the specified face. * @param nameId The ID of the name to get. * @param language The language of the name to get. * @returns The name string. */ getName(nameId, language) { const sp = Module.stackSave(); const languagePtr = language_from_string(language); const nameLen = exports.hb_ot_name_get_utf16(this.ptr, nameId, languagePtr, 0, 0) + 1; const textSizePtr = Module.stackAlloc(4); const textPtr = exports.malloc(nameLen * 2); Module.HEAPU32[textSizePtr / 4] = nameLen; exports.hb_ot_name_get_utf16(this.ptr, nameId, languagePtr, textSizePtr, textPtr); const name = utf16_ptr_to_string(textPtr, nameLen - 1); exports.free(textPtr); Module.stackRestore(sp); return name; } /** * Get the name IDs of the specified feature. * @param table The table to query, either "GSUB" or "GPOS". * @param featureIndex The index of the feature to query. * @returns An object with name IDs, or undefined if not found. */ getFeatureNameIds(table, featureIndex) { const sp = Module.stackSave(); const tableTag = hb_tag(table); const labelIdPtr = Module.stackAlloc(4); const tooltipIdPtr = Module.stackAlloc(4); const sampleIdPtr = Module.stackAlloc(4); const numNamedParametersPtr = Module.stackAlloc(4); const firstParameterIdPtr = Module.stackAlloc(4); const found = exports.hb_ot_layout_feature_get_name_ids(this.ptr, tableTag, featureIndex, labelIdPtr, tooltipIdPtr, sampleIdPtr, numNamedParametersPtr, firstParameterIdPtr); let names; if (found) { const uiLabelNameId = Module.HEAPU32[labelIdPtr / 4]; const uiTooltipTextNameId = Module.HEAPU32[tooltipIdPtr / 4]; const sampleTextNameId = Module.HEAPU32[sampleIdPtr / 4]; const numNamedParameters = Module.HEAPU32[numNamedParametersPtr / 4]; const firstParameterId = Module.HEAPU32[firstParameterIdPtr / 4]; names = { paramUiLabelNameIds: Array.from({ length: numNamedParameters }, (_, i) => firstParameterId + i) }; if (uiLabelNameId != HB_OT_NAME_ID_INVALID) names.uiLabelNameId = uiLabelNameId; if (uiTooltipTextNameId != HB_OT_NAME_ID_INVALID) names.uiTooltipTextNameId = uiTooltipTextNameId; if (sampleTextNameId != HB_OT_NAME_ID_INVALID) names.sampleTextNameId = sampleTextNameId; } Module.stackRestore(sp); return names; } }; //#endregion //#region src/font.ts /** * An object representing a {@link https://harfbuzz.github.io/harfbuzz-hb-font.html | HarfBuzz font}. * A font represents a face at a specific size and with certain other * parameters (pixels-per-em, variation settings) specified. Fonts are the * primary input to the shaping process. */ var Font = class Font { constructor(arg) { this.drawPtrs = { pathBuffer: "" }; if (typeof arg === "number") this.ptr = exports.hb_font_reference(arg); else { this.ptr = exports.hb_font_create(arg.ptr); this._face = arg; } const ptr = this.ptr; const drawState = this.drawPtrs; registry.register(this, () => { exports.hb_font_destroy(ptr); if (drawState.drawFuncsPtr) { exports.hb_draw_funcs_destroy(drawState.drawFuncsPtr); Module.removeFunction(drawState.moveToPtr); Module.removeFunction(drawState.lineToPtr); Module.removeFunction(drawState.cubicToPtr); Module.removeFunction(drawState.quadToPtr); Module.removeFunction(drawState.closePathPtr); } }); } /** The {@link Face} associated with this font. */ get face() { if (!this._face) this._face = new Face(exports.hb_font_get_face(this.ptr)); return this._face; } /** * Create a sub font that inherits this font's properties. * @returns A new Font object representing the sub font. */ subFont() { return new Font(exports.hb_font_create_sub_font(this.ptr)); } /** * Return font horizontal extents. * @returns Object with ascender, descender, and lineGap properties. */ hExtents() { const sp = Module.stackSave(); const extentsPtr = Module.stackAlloc(48); exports.hb_font_get_h_extents(this.ptr, extentsPtr); const extents = { ascender: Module.HEAP32[extentsPtr / 4], descender: Module.HEAP32[extentsPtr / 4 + 1], lineGap: Module.HEAP32[extentsPtr / 4 + 2] }; Module.stackRestore(sp); return extents; } /** * Return font vertical extents. * @returns Object with ascender, descender, and lineGap properties. */ vExtents() { const sp = Module.stackSave(); const extentsPtr = Module.stackAlloc(48); exports.hb_font_get_v_extents(this.ptr, extentsPtr); const extents = { ascender: Module.HEAP32[extentsPtr / 4], descender: Module.HEAP32[extentsPtr / 4 + 1], lineGap: Module.HEAP32[extentsPtr / 4 + 2] }; Module.stackRestore(sp); return extents; } /** * Return glyph name. * @param glyphId ID of the requested glyph in the font. * @returns The glyph name string. */ glyphName(glyphId) { const sp = Module.stackSave(); const strSize = 256; const strPtr = Module.stackAlloc(strSize); exports.hb_font_glyph_to_string(this.ptr, glyphId, strPtr, strSize); const name = utf8_ptr_to_string(strPtr); Module.stackRestore(sp); return name; } /** * Return a glyph as an SVG path string. * @param glyphId ID of the requested glyph in the font. * @returns SVG path data string. */ glyphToPath(glyphId) { const ds = this.drawPtrs; if (!ds.drawFuncsPtr) { const moveTo = (dfuncs, draw_data, draw_state, to_x, to_y, user_data) => { ds.pathBuffer += `M${to_x},${to_y}`; }; const lineTo = (dfuncs, draw_data, draw_state, to_x, to_y, user_data) => { ds.pathBuffer += `L${to_x},${to_y}`; }; const cubicTo = (dfuncs, draw_data, draw_state, c1_x, c1_y, c2_x, c2_y, to_x, to_y, user_data) => { ds.pathBuffer += `C${c1_x},${c1_y} ${c2_x},${c2_y} ${to_x},${to_y}`; }; const quadTo = (dfuncs, draw_data, draw_state, c_x, c_y, to_x, to_y, user_data) => { ds.pathBuffer += `Q${c_x},${c_y} ${to_x},${to_y}`; }; const closePath = (dfuncs, draw_data, draw_state, user_data) => { ds.pathBuffer += "Z"; }; ds.moveToPtr = Module.addFunction(moveTo, "viiiffi"); ds.lineToPtr = Module.addFunction(lineTo, "viiiffi"); ds.cubicToPtr = Module.addFunction(cubicTo, "viiiffffffi"); ds.quadToPtr = Module.addFunction(quadTo, "viiiffffi"); ds.closePathPtr = Module.addFunction(closePath, "viiii"); ds.drawFuncsPtr = exports.hb_draw_funcs_create(); exports.hb_draw_funcs_set_move_to_func(ds.drawFuncsPtr, ds.moveToPtr, 0, 0); exports.hb_draw_funcs_set_line_to_func(ds.drawFuncsPtr, ds.lineToPtr, 0, 0); exports.hb_draw_funcs_set_cubic_to_func(ds.drawFuncsPtr, ds.cubicToPtr, 0, 0); exports.hb_draw_funcs_set_quadratic_to_func(ds.drawFuncsPtr, ds.quadToPtr, 0, 0); exports.hb_draw_funcs_set_close_path_func(ds.drawFuncsPtr, ds.closePathPtr, 0, 0); } ds.pathBuffer = ""; exports.hb_font_draw_glyph(this.ptr, glyphId, ds.drawFuncsPtr, 0); return ds.pathBuffer; } /** * Return glyph horizontal advance. * @param glyphId ID of the requested glyph in the font. * @returns The horizontal advance width. */ glyphHAdvance(glyphId) { return exports.hb_font_get_glyph_h_advance(this.ptr, glyphId); } /** * Return glyph vertical advance. * @param glyphId ID of the requested glyph in the font. * @returns The vertical advance height. */ glyphVAdvance(glyphId) { return exports.hb_font_get_glyph_v_advance(this.ptr, glyphId); } /** * Return glyph horizontal origin. * @param glyphId ID of the requested glyph in the font. * @returns [x, y] origin coordinates, or undefined if not available. */ glyphHOrigin(glyphId) { const sp = Module.stackSave(); const xPtr = Module.stackAlloc(4); const yPtr = Module.stackAlloc(4); let origin; if (exports.hb_font_get_glyph_h_origin(this.ptr, glyphId, xPtr, yPtr)) origin = [Module.HEAP32[xPtr / 4], Module.HEAP32[yPtr / 4]]; Module.stackRestore(sp); return origin; } /** * Return glyph vertical origin. * @param glyphId ID of the requested glyph in the font. * @returns [x, y] origin coordinates, or undefined if not available. */ glyphVOrigin(glyphId) { const sp = Module.stackSave(); const xPtr = Module.stackAlloc(4); const yPtr = Module.stackAlloc(4); let origin; if (exports.hb_font_get_glyph_v_origin(this.ptr, glyphId, xPtr, yPtr)) origin = [Module.HEAP32[xPtr / 4], Module.HEAP32[yPtr / 4]]; Module.stackRestore(sp); return origin; } /** * Return glyph extents. * @param glyphId ID of the requested glyph in the font. * @returns An object with xBearing, yBearing, width, and height, or undefined. */ glyphExtents(glyphId) { const sp = Module.stackSave(); const extentsPtr = Module.stackAlloc(16); let extents; if (exports.hb_font_get_glyph_extents(this.ptr, glyphId, extentsPtr)) extents = { xBearing: Module.HEAP32[extentsPtr / 4], yBearing: Module.HEAP32[extentsPtr / 4 + 1], width: Module.HEAP32[extentsPtr / 4 + 2], height: Module.HEAP32[extentsPtr / 4 + 3] }; Module.stackRestore(sp); return extents; } /** * Fetches the glyph ID for a Unicode code point in the specified * font, with an optional variation selector. * * If `variationSelector` is 0, it is equivalent to * {@link Font.nominalGlyph}; otherwise it is equivalent to * {@link Font.variationGlyph}. * * @param unicode The Unicode code point to query. * @param variationSelector A variation-selector code point. * @returns The glyph ID, or undefined if not found. */ glyph(unicode, variationSelector = 0) { const sp = Module.stackSave(); const glyphIdPtr = Module.stackAlloc(4); let glyphId; if (exports.hb_font_get_glyph(this.ptr, unicode, variationSelector, glyphIdPtr)) glyphId = Module.HEAPU32[glyphIdPtr / 4]; Module.stackRestore(sp); return glyphId; } /** * Fetches the nominal glyph ID for a Unicode code point in the * specified font. * * This version of the function should not be used to fetch glyph IDs * for code points modified by variation selectors. For variation-selector * support, use {@link Font.variationGlyph} or {@link Font.glyph}. * * @param unicode The Unicode code point to query. * @returns The glyph ID, or undefined if not found. */ nominalGlyph(unicode) { const sp = Module.stackSave(); const glyphIdPtr = Module.stackAlloc(4); let glyphId; if (exports.hb_font_get_nominal_glyph(this.ptr, unicode, glyphIdPtr)) glyphId = Module.HEAPU32[glyphIdPtr / 4]; Module.stackRestore(sp); return glyphId; } /** * Fetches the glyph ID for a Unicode code point when followed by * by the specified variation-selector code point, in the specified * font. * * @param unicode The Unicode code point to query. * @param variationSelector The variation-selector code point to query. * @returns The glyph ID, or undefined if not found. */ variationGlyph(unicode, variationSelector) { const sp = Module.stackSave(); const glyphIdPtr = Module.stackAlloc(4); let glyphId; if (exports.hb_font_get_variation_glyph(this.ptr, unicode, variationSelector, glyphIdPtr)) glyphId = Module.HEAPU32[glyphIdPtr / 4]; Module.stackRestore(sp); return glyphId; } /** * Return glyph ID from name. * @param name Name of the requested glyph in the font. * @returns The glyph ID, or undefined if not found. */ glyphFromName(name) { const sp = Module.stackSave(); const glyphIdPtr = Module.stackAlloc(4); const namePtr = string_to_utf8_ptr(name); let glyphId; if (exports.hb_font_get_glyph_from_name(this.ptr, namePtr.ptr, namePtr.length, glyphIdPtr)) glyphId = Module.HEAPU32[glyphIdPtr / 4]; namePtr.free(); Module.stackRestore(sp); return glyphId; } /** * Return a glyph as a JSON path string * based on format described on https://svgwg.org/specs/paths/#InterfaceSVGPathSegment * @param glyphId ID of the requested glyph in the font. * @returns An array of path segment objects with type and values. */ glyphToJson(glyphId) { return this.glyphToPath(glyphId).replace(/([MLQCZ])/g, "|$1 ").split("|").filter((x) => x.length).map((x) => { const [type, ...values] = x.split(/[ ,]/g).filter((s) => s.length); return { type, values: values.map(Number) }; }); } /** * Set the font's scale factor, affecting the position values returned from * shaping. * @param xScale Units to scale in the X dimension. * @param yScale Units to scale in the Y dimension. */ setScale(xScale, yScale) { exports.hb_font_set_scale(this.ptr, xScale, yScale); } /** * Applies a list of font-variation settings to a font. * * Note that this overrides all existing variations set on the font. * Axes not included in `variations` will be effectively set to their * default values. * * @param variations Array of variation settings to apply. */ setVariations(variations) { const sp = Module.stackSave(); const vars = Module.stackAlloc(8 * variations.length); variations.forEach((variation, i) => { variation.writeTo(vars + i * 8); }); exports.hb_font_set_variations(this.ptr, vars, variations.length); Module.stackRestore(sp); } /** Set the font's font functions. */ setFuncs(fontFuncs) { exports.hb_font_set_funcs(this.ptr, fontFuncs.ptr); } /** * Fetches the optical bound of a glyph positioned at the margin of text. * The direction identifies which edge of the glyph to query. * @param lookupIndex Index of the feature lookup to query. * @param direction Edge of the glyph to query. * @param glyph A glyph id. * @returns Adjustment value. Negative values mean the glyph will stick out of the margin. */ getLookupOpticalBound(lookupIndex, direction, glyph) { return exports.hb_ot_layout_lookup_get_optical_bound(this.ptr, lookupIndex, direction, glyph); } }; //#endregion //#region src/font-funcs.ts /** * An object representing {@link https://harfbuzz.github.io/harfbuzz-hb-font.html | HarfBuzz font functions}. * Font functions define the methods used by a Font for lower-level queries * like glyph advances and extents. HarfBuzz provides built-in default * implementations which can be selectively overridden. */ var FontFuncs = class { constructor() { this.ptr = exports.hb_font_funcs_create(); track(this, exports.hb_font_funcs_destroy); } /** * Set the font's glyph extents function. * @param func The callback receives a Font and glyph ID. It should return * an object with xBearing, yBearing, width, and height, or undefined on failure. */ setGlyphExtentsFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, glyph, extentsPtr, user_data) => { const extents = func(new Font(fontPtr), glyph); if (extents) { Module.HEAP32[extentsPtr / 4] = extents.xBearing; Module.HEAP32[extentsPtr / 4 + 1] = extents.yBearing; Module.HEAP32[extentsPtr / 4 + 2] = extents.width; Module.HEAP32[extentsPtr / 4 + 3] = extents.height; return 1; } return 0; }, "ippipp"); exports.hb_font_funcs_set_glyph_extents_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph from name function. * @param func The callback receives a Font and glyph name. It should return * the glyph ID, or undefined on failure. */ setGlyphFromNameFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, namePtr, len, glyphPtr, user_data) => { const glyph = func(new Font(fontPtr), utf8_ptr_to_string(namePtr, len)); if (glyph) { Module.HEAPU32[glyphPtr / 4] = glyph; return 1; } return 0; }, "ipppipp"); exports.hb_font_funcs_set_glyph_from_name_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph horizontal advance function. * @param func The callback receives a Font and glyph ID. It should return * the horizontal advance of the glyph. */ setGlyphHAdvanceFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, glyph, user_data) => { return func(new Font(fontPtr), glyph); }, "ippip"); exports.hb_font_funcs_set_glyph_h_advance_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph vertical advance function. * @param func The callback receives a Font and glyph ID. It should return * the vertical advance of the glyph. */ setGlyphVAdvanceFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, glyph, user_data) => { return func(new Font(fontPtr), glyph); }, "ippip"); exports.hb_font_funcs_set_glyph_v_advance_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph horizontal origin function. * @param func The callback receives a Font and glyph ID. It should return * the [x, y] horizontal origin of the glyph, or undefined on failure. */ setGlyphHOriginFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, glyph, xPtr, yPtr, user_data) => { const origin = func(new Font(fontPtr), glyph); if (origin) { Module.HEAP32[xPtr / 4] = origin[0]; Module.HEAP32[yPtr / 4] = origin[1]; return 1; } return 0; }, "ippippp"); exports.hb_font_funcs_set_glyph_h_origin_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph vertical origin function. * @param func The callback receives a Font and glyph ID. It should return * the [x, y] vertical origin of the glyph, or undefined on failure. */ setGlyphVOriginFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, glyph, xPtr, yPtr, user_data) => { const origin = func(new Font(fontPtr), glyph); if (origin) { Module.HEAP32[xPtr / 4] = origin[0]; Module.HEAP32[yPtr / 4] = origin[1]; return 1; } return 0; }, "ippippp"); exports.hb_font_funcs_set_glyph_v_origin_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph horizontal kerning function. * @param func The callback receives a Font, first glyph ID, and second glyph ID. * It should return the horizontal kerning of the glyphs. */ setGlyphHKerningFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, firstGlyph, secondGlyph, user_data) => { return func(new Font(fontPtr), firstGlyph, secondGlyph); }, "ippiip"); exports.hb_font_funcs_set_glyph_h_kerning_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's glyph name function. * @param func The callback receives a Font and glyph ID. It should return * the name of the glyph, or undefined on failure. */ setGlyphNameFunc(func) { const utf8Encoder = new TextEncoder(); const funcPtr = Module.addFunction((fontPtr, font_data, glyph, namePtr, size, user_data) => { const name = func(new Font(fontPtr), glyph); if (name) { utf8Encoder.encodeInto(name, Module.HEAPU8.subarray(namePtr, namePtr + size)); return 1; } return 0; }, "ippipip"); exports.hb_font_funcs_set_glyph_name_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's nominal glyph function. * @param func The callback receives a Font and unicode code point. It should * return the nominal glyph of the unicode, or undefined on failure. */ setNominalGlyphFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, unicode, glyphPtr, user_data) => { const glyph = func(new Font(fontPtr), unicode); if (glyph) { Module.HEAPU32[glyphPtr / 4] = glyph; return 1; } return 0; }, "ippipp"); exports.hb_font_funcs_set_nominal_glyph_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's variation glyph function. * @param func The callback receives a Font, unicode code point, and variation * selector. It should return the variation glyph, or undefined on failure. */ setVariationGlyphFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, unicode, variationSelector, glyphPtr, user_data) => { const glyph = func(new Font(fontPtr), unicode, variationSelector); if (glyph) { Module.HEAPU32[glyphPtr / 4] = glyph; return 1; } return 0; }, "ippiipp"); exports.hb_font_funcs_set_variation_glyph_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's horizontal extents function. * @param func The callback receives a Font. It should return an object with * ascender, descender, and lineGap, or undefined on failure. */ setFontHExtentsFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, extentsPtr, user_data) => { const extents = func(new Font(fontPtr)); if (extents) { Module.HEAP32[extentsPtr / 4] = extents.ascender; Module.HEAP32[extentsPtr / 4 + 1] = extents.descender; Module.HEAP32[extentsPtr / 4 + 2] = extents.lineGap; return 1; } return 0; }, "ipppp"); exports.hb_font_funcs_set_font_h_extents_func(this.ptr, funcPtr, 0, 0); } /** * Set the font's vertical extents function. * @param func The callback receives a Font. It should return an object with * ascender, descender, and lineGap, or undefined on failure. */ setFontVExtentsFunc(func) { const funcPtr = Module.addFunction((fontPtr, font_data, extentsPtr, user_data) => { const extents = func(new Font(fontPtr)); if (extents) { Module.HEAP32[extentsPtr / 4] = extents.ascender; Module.HEAP32[extentsPtr / 4 + 1] = extents.descender; Module.HEAP32[extentsPtr / 4 + 2] = extents.lineGap; return 1; } return 0; }, "ipppp"); exports.hb_font_funcs_set_font_v_extents_func(this.ptr, funcPtr, 0, 0); } }; //#endregion //#region src/buffer.ts const BufferContentType = { INVALID: 0, UNICODE: 1, GLYPHS: 2 }; const BufferSerializeFlag = { DEFAULT: 0, NO_CLUSTERS: 1, NO_POSITIONS: 2, NO_GLYPH_NAMES: 4, GLYPH_EXTENTS: 8, GLYPH_FLAGS: 16, NO_ADVANCES: 32, DEFINED: 63 }; const BufferFlag = { DEFAULT: 0, BOT: 1, EOT: 2, PRESERVE_DEFAULT_IGNORABLES: 4, REMOVE_DEFAULT_IGNORABLES: 8, DO_NOT_INSERT_DOTTED_CIRCLE: 16, VERIFY: 32, PRODUCE_UNSAFE_TO_CONCAT: 64, PRODUCE_SAFE_TO_INSERT_TATWEEL: 128, DEFINED: 255 }; const Direction = { INVALID: 0, LTR: 4, RTL: 5, TTB: 6, BTT: 7 }; const ClusterLevel = { MONOTONE_GRAPHEMES: 0, MONOTONE_CHARACTERS: 1, CHARACTERS: 2, GRAPHEMES: 3, DEFAULT: 0 }; const BufferSerializeFormat = { INVALID: 0, TEXT: hb_tag("TEXT"), JSON: hb_tag("JSON") }; /** * An object representing a {@link https://harfbuzz.github.io/harfbuzz-hb-buffer.html | HarfBuzz buffer}. * A buffer holds the input text and its properties before shaping, and the * output glyphs and their information after shaping. */ var Buffer = class Buffer { /** * @param existingPtr @internal Wrap an existing buffer pointer. */ constructor(existingPtr) { if (existingPtr != void 0) this.ptr = exports.hb_buffer_reference(existingPtr); else this.ptr = exports.hb_buffer_create(); track(this, exports.hb_buffer_destroy); } /** * Appends a character with the Unicode value of `codePoint` to the buffer, * and gives it the initial cluster value of `cluster`. Clusters can be any * thing the client wants, they are usually used to refer to the index of the * character in the input text stream and are output in the `cluster` field * of {@link GlyphInfo}. * * This function does not check the validity of `codePoint`, it is up to the * caller to ensure it is a valid Unicode code point. * @param codePoint A Unicode code point. * @param cluster The cluster value of `codePoint`. */ add(codePoint, cluster) { exports.hb_buffer_add(this.ptr, codePoint, cluster); } /** * Add text to the buffer. * @param text Text to be added to the buffer. * @param itemOffset The offset of the first character to add to the buffer. * @param itemLength The number of characters to add to the buffer, or omit for the end of text. */ addText(text, itemOffset = 0, itemLength) { const str = string_to_utf16_ptr(text); exports.hb_buffer_add_utf16(this.ptr, str.ptr, str.length, itemOffset, itemLength ?? str.length); str.free(); } /** * Add code points to the buffer. * @param codePoints Array of code points to be added to the buffer. * @param itemOffset The offset of the first code point to add to the buffer. * @param itemLength The number of code points to add to the buffer, or omit for the end of the array. */ addCodePoints(codePoints, itemOffset = 0, itemLength) { const codePointsPtr = exports.malloc(codePoints.length * 4); Module.HEAPU32.subarray(codePointsPtr / 4, codePointsPtr / 4 + codePoints.length).set(codePoints); exports.hb_buffer_add_codepoints(this.ptr, codePointsPtr, codePoints.length, itemOffset, itemLength ?? codePoints.length); exports.free(codePointsPtr); } /** * Set buffer script, language and direction. * * This needs to be done before shaping. */ guessSegmentProperties() { exports.hb_buffer_guess_segment_properties(this.ptr); } /** * Set buffer direction explicitly. * @param dir A {@link Direction} value. */ setDirection(dir) { exports.hb_buffer_set_direction(this.ptr, dir); } /** * Set buffer flags explicitly. * @param flags A combination of {@link BufferFlag} values (OR them together). */ setFlags(flags) { exports.hb_buffer_set_flags(this.ptr, flags); } /** * Set buffer language explicitly. * @param language The buffer language */ setLanguage(language) { const str = string_to_ascii_ptr(language); exports.hb_buffer_set_language(this.ptr, exports.hb_language_from_string(str.ptr, -1)); str.free(); } /** * Set buffer script explicitly. * @param script The buffer script */ setScript(script) { const str = string_to_ascii_ptr(script); exports.hb_buffer_set_script(this.ptr, exports.hb_script_from_string(str.ptr, -1)); str.free(); } /** * Set the HarfBuzz clustering level. * * Affects the cluster values returned from shaping. * @param level A {@link ClusterLevel} value. See the HarfBuzz manual chapter on Clusters. */ setClusterLevel(level) { exports.hb_buffer_set_cluster_level(this.ptr, level); } /** Reset the buffer to its initial status. */ reset() { exports.hb_buffer_reset(this.ptr); } /** * Similar to reset(), but does not clear the Unicode functions and the * replacement code point. */ clearContents() { exports.hb_buffer_clear_contents(this.ptr); } /** * Set message func. * @param func The function to set. It receives the buffer, font, and message * string as arguments. Returning false will skip this shaping step and move * to the next one. */ setMessageFunc(func) { const traceFunc = (bufferPtr, fontPtr, messagePtr, user_data) => { const message = utf8_ptr_to_string(messagePtr); return func(new Buffer(bufferPtr), new Font(fontPtr), message) ? 1 : 0; }; const traceFuncPtr = Module.addFunction(traceFunc, "iiiii"); exports.hb_buffer_set_message_func(this.ptr, traceFuncPtr, 0, 0); } /** * Get the the number of items in the buffer. * @returns The buffer length. */ getLength() { return exports.hb_buffer_get_length(this.ptr); } /** * Get the glyph information from the buffer. * @returns An array of {@link GlyphInfo} objects. */ getGlyphInfos() { const infosPtr = exports.hb_buffer_get_glyph_infos(this.ptr, 0); const infosArray = Module.HEAPU32.subarray(infosPtr / 4, infosPtr / 4 + this.getLength() * 5); const infos = []; for (let i = 0; i < infosArray.length; i += 5) infos.push({ codepoint: infosArray[i], cluster: infosArray[i + 2], flags: exports.hb_glyph_info_get_glyph_flags(infosPtr + i * 4) }); return infos; } /** * Get the glyph positions from the buffer. * @returns An array of {@link GlyphPosition} objects. */ getGlyphPositions() { const positionsPtr32 = exports.hb_buffer_get_glyph_positions(this.ptr, 0) / 4; if (positionsPtr32 == 0) return []; const positionsArray = Module.HEAP32.subarray(positionsPtr32, positionsPtr32 + this.getLength() * 5); const positions = []; for (let i = 0; i < positionsArray.length; i += 5) positions.push({ xAdvance: positionsArray[i], yAdvance: positionsArray[i + 1], xOffset: positionsArray[i + 2], yOffset: positionsArray[i + 3] }); return positions; } /** * Get the glyph information and positions from the buffer. * @returns The glyph information and positions. * * The glyph information is returned as an array of objects with the * properties from getGlyphInfos and getGlyphPositions combined. */ getGlyphInfosAndPositions() { const infosPtr = exports.hb_buffer_get_glyph_infos(this.ptr, 0); const infosArray = Module.HEAPU32.subarray(infosPtr / 4, infosPtr / 4 + this.getLength() * 5); const positionsPtr32 = exports.hb_buffer_get_glyph_positions(this.ptr, 0) / 4; const positionsArray = positionsPtr32 ? Module.HEAP32.subarray(positionsPtr32, positionsPtr32 + this.getLength() * 5) : void 0; const out = []; for (let i = 0; i < infosArray.length; i += 5) { const info = { codepoint: infosArray[i], cluster: infosArray[i + 2], flags: exports.hb_glyph_info_get_glyph_flags(infosPtr + i * 4) }; for (const [name, idx] of [ ["mask", 1], ["var1", 3], ["var2", 4] ]) Object.defineProperty(info, name, { value: infosArray[i + idx], enumerable: false }); if (positionsArray) { info.xAdvance = positionsArray[i]; info.yAdvance = positionsArray[i + 1]; info.xOffset = positionsArray[i + 2]; info.yOffset = positionsArray[i + 3]; Object.defineProperty(info, "var", { value: positionsArray[i + 4], enumerable: false }); } out.push(info); } return out; } /** * Update the glyph positions in the buffer. * WARNING: Do not use unless you know what you are doing. */ updateGlyphPositions(positions) { const positionsPtr32 = exports.hb_buffer_get_glyph_positions(this.ptr, 0) / 4; if (positionsPtr32 == 0) return; const len = Math.min(positions.length, this.getLength()); const positionsArray = Module.HEAP32.subarray(positionsPtr32, positionsPtr32 + len * 5); for (let i = 0; i < len; i++) { positionsArray[i * 5] = positions[i].xAdvance; positionsArray[i * 5 + 1] = positions[i].yAdvance; positionsArray[i * 5 + 2] = positions[i].xOffset; positionsArray[i * 5 + 3] = positions[i].yOffset; } } /** * Serialize the buffer contents to a string. * @param options Serialization options: * - `font`: the font to use for serialization; * - `start`: the starting index of the glyphs (default `0`); * - `end`: the ending index of the glyphs (default end of buffer); * - `format`: a {@link BufferSerializeFormat} value (default `TEXT`); * - `flags`: a combination of {@link BufferSerializeFlag} values (default `0`). * @returns The serialized buffer contents. */ serialize(options = {}) { let { font, start = 0, end, format = BufferSerializeFormat.TEXT, flags = 0 } = options; const sp = Module.stackSave(); const endPos = end ?? this.getLength(); const bufLen = 32 * 1024; const bufPtr = exports.malloc(bufLen); const bufConsumedPtr = Module.stackAlloc(4); let result = ""; while (start < endPos) { start += exports.hb_buffer_serialize(this.ptr, start, endPos, bufPtr, bufLen, bufConsumedPtr, font ? font.ptr : 0, format, flags); const bufConsumed = Module.HEAPU32[bufConsumedPtr / 4]; if (bufConsumed == 0) break; result += utf8_ptr_to_string(bufPtr, bufConsumed); } exports.free(bufPtr); Module.stackRestore(sp); return result; } /** * Return the buffer content type. * * @returns The buffer content type as a {@link BufferContentType} value. */ getContentType() { return exports.hb_buffer_get_content_type(this.ptr); } }; //#endregion //#region src/feature.ts /** * A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-t | HarfBuzz feature}. * * The structure that holds information about requested feature application. * The feature will be applied with the given value to all glyphs which are in * clusters between {@link Feature.start} (inclusive) and {@link Feature.end} * (exclusive). Setting `start` to `0` and `end` to `0xffffffff` specifies that * the feature always applies to the entire buffer. */ var Feature = class Feature { static { this.GLOBAL_START = 0; } static { this.GLOBAL_END = 4294967295; } constructor(tag, value = 1, start = Feature.GLOBAL_START, end = Feature.GLOBAL_END) { this.tag = tag; this.value = value; this.start = start; this.end = end; } /** * Parses a string into a Feature. * * The format for specifying feature strings follows. All valid CSS * font-feature-settings values other than `normal` and the global values are * also accepted, though not documented below. CSS string escapes are not * supported. * * The range indices refer to the positions between Unicode characters. The * position before the first character is always 0. * * The format is Python-esque. Here is how it all works: * * | Syntax | Value | Start | End | Meaning | * | ------------- | ----- | ----- | --- | -------------------------------- | * | `kern` | 1 | 0 | ∞ | Turn feature on | * | `+kern` | 1 | 0 | ∞ | Turn feature on | * | `-kern` | 0 | 0 | ∞ | Turn feature off | * | `kern=0` | 0 | 0 | ∞ | Turn feature off | * | `kern=1` | 1 | 0 | ∞ | Turn feature on | * | `aalt=2` | 2 | 0 | ∞ | Choose 2nd alternate | * | `kern[]` | 1 | 0 | ∞ | Turn feature on | * | `kern[:]` | 1 | 0 | ∞ | Turn feature on | * | `kern[5:]` | 1 | 5 | ∞ | Turn feature on, partial | * | `kern[:5]` | 1 | 0 | 5 | Turn feature on, partial | * | `kern[3:5]` | 1 | 3 | 5 | Turn feature on, range | * | `kern[3]` | 1 | 3 | 3+1 | Turn feature on, single char | * | `aalt[3:5]=2` | 2 | 3 | 5 | Turn 2nd alternate on for range | * * @param str The string to parse. * @returns A Feature, or undefined if the string is not a valid feature. */ static fromString(str) { const sp = Module.stackSave(); const featurePtr = Module.stackAlloc(16); const strPtr = string_to_ascii_ptr(str); let feature; if (exports.hb_feature_from_string(strPtr.ptr, -1, featurePtr)) feature = new Feature(hb_untag(Module.HEAPU32[featurePtr / 4]), Module.HEAPU32[featurePtr / 4 + 1], Module.HEAPU32[featurePtr / 4 + 2], Module.HEAPU32[featurePtr / 4 + 3]); strPtr.free(); Module.stackRestore(sp); return feature; } /** * Converts the feature to a string in the format understood by * {@link Feature.fromString}. * * Note that the feature value will be omitted if it is `1`, but the string * won't include any whitespace. * * @returns The feature string. */ toString() { const sp = Module.stackSave(); const featurePtr = Module.stackAlloc(16); this.writeTo(featurePtr); const bufLen = 128; const bufPtr = Module.stackAlloc(bufLen); exports.hb_feature_to_string(featurePtr, bufPtr, bufLen); const result = utf8_ptr_to_string(bufPtr); Module.stackRestore(sp); return result; } /** @internal Write this feature into the given hb_feature_t pointer. */ writeTo(ptr) { Module.HEAPU32[ptr / 4] = hb_tag(this.tag); Module.HEAPU32[ptr / 4 + 1] = this.value; Module.HEAPU32[ptr / 4 + 2] = this.start; Module.HEAPU32[ptr / 4 + 3] = this.end; } }; //#endregion //#region src/variation.ts /** * A {@link https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-variation-t | HarfBuzz variation}. * * Data type for holding variation data. Registered OpenType variation-axis * tags are listed in * {@link https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg | OpenType Axis Tag Registry}. */ var Variation = class Variation { constructor(tag, value = 0) { this.tag = tag; this.value = value; } /** * Parses a string into a Variation. * * The format for specifying variation settings follows. All valid CSS * font-variation-settings values other than `normal` and `inherited` are * also accepted, though, not documented below. * * The format is a tag, optionally followed by an equals sign, followed by a * number. For example `wght=500`, or `slnt=-7.5`. * * @param str The string to parse. * @returns A Variation, or undefined if the string is not a valid variation. */ static fromString(str) { const sp = Module.stackSave(); const variationPtr = Module.stackAlloc(8); const strPtr = string_to_ascii_ptr(str); let variation; if (exports.hb_variation_from_string(strPtr.ptr, -1, variationPtr)) variation = new Variation(hb_untag(Module.HEAPU32[variationPtr / 4]), Module.HEAPF32[variationPtr / 4 + 1]); strPtr.free(); Module.stackRestore(sp); return variation; } /** * Converts the variation to a string in the format understood by * {@link Variation.fromString}. * * Note that the string won't include any whitespace. * * @returns The variation string. */ toString() { const sp = Module.stackSave(); const variationPtr = Module.stackAlloc(8); this.writeTo(variationPtr); const bufLen = 128; const bufPtr = Module.stackAlloc(bufLen); exports.hb_variation_to_string(variationPtr, bufPtr, bufLen); const result = utf8_ptr_to_string(bufPtr); Module.stackRestore(sp); return result; } /** @internal Write this variation into the given hb_variation_t pointer. */ writeTo(ptr) { Module.HEAPU32[ptr / 4] = hb_tag(this.tag); Module.HEAPF32[ptr / 4 + 1] = this.value; } }; //#endregion //#region src/shape.ts const TracePhase = { DONT_STOP: 0, GSUB: 1, GPOS: 2 }; /** * Shape a buffer with a given font. * * Converts the Unicode text in the buffer into positioned glyphs. * The buffer is modified in place. * * @param font The Font to shape with. * @param buffer The Buffer containing text to shape, suitably prepared * (text added, segment properties set). * @param features An array of {@link Feature} values to apply. */ function shape(font, buffer, features) { const featuresLen = features?.length ?? 0; const sp = Module.stackSave(); let featuresPtr = 0; if (featuresLen) { featuresPtr = Module.stackAlloc(16 * featuresLen); features.forEach((feature, i) => { feature.writeTo(featuresPtr + i * 16); }); } exports.hb_shape(font.ptr, buffer.ptr, featuresPtr, featuresLen); Module.stackRestore(sp); } /** * Shape a buffer with a giv