harfbuzzjs
Version:
Minimal version of HarfBuzz for JavaScript use
1,421 lines • 53 kB
JavaScript
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