diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1e2600..27de581 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: branches: [ "main" ] env: - EM_VERSION: 3.1.16 + EM_VERSION: 3.1.56 EM_CACHE_FOLDER: 'emsdk-cache' jobs: @@ -28,7 +28,7 @@ jobs: path: ${{env.EM_CACHE_FOLDER}} key: ${{env.EM_VERSION}}-${{runner.os}} - name: Setup Emscripten - uses: mymindstorm/setup-emsdk@v11 + uses: mymindstorm/setup-emsdk@v14 with: version: ${{env.EM_VERSION}} actions-cache-folder: ${{env.EM_CACHE_FOLDER}} diff --git a/.gitignore b/.gitignore index 9a95569..41316c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.wasm +hb.js .DS_Store \ No newline at end of file diff --git a/build.sh b/build.sh index 1a98052..3589a72 100755 --- a/build.sh +++ b/build.sh @@ -15,7 +15,11 @@ em++ \ -DHB_CONFIG_OVERRIDE_H=\"config-override.h\" \ -DHB_EXPERIMENTAL_API \ --no-entry \ - -s EXPORTED_FUNCTIONS=@hbjs.symbols \ + -s MODULARIZE \ + -s EXPORTED_FUNCTIONS=@hb.symbols \ + -s EXPORTED_RUNTIME_METHODS='["addFunction", "wasmMemory", "wasmExports"]' \ -s INITIAL_MEMORY=65MB \ - -o hb.wasm \ - hbjs.cc + -s ALLOW_TABLE_GROWTH \ + -lexports.js \ + -o hb.js \ + harfbuzz/src/harfbuzz.cc diff --git a/harfbuzz b/harfbuzz index e079dd2..2b3631a 160000 --- a/harfbuzz +++ b/harfbuzz @@ -1 +1 @@ -Subproject commit e079dd20304bacd8d753eee2ceacbaa1a3978a34 +Subproject commit 2b3631a866b3077d9d675caa4ec9010b342b5a7c diff --git a/hbjs.symbols b/hb.symbols similarity index 71% rename from hbjs.symbols rename to hb.symbols index 8e03d09..b78e99b 100644 --- a/hbjs.symbols +++ b/hb.symbols @@ -9,12 +9,15 @@ _hb_buffer_destroy _hb_buffer_get_glyph_infos _hb_buffer_get_glyph_positions _hb_buffer_get_length +_hb_buffer_get_content_type _hb_buffer_guess_segment_properties _hb_buffer_set_cluster_level _hb_buffer_set_direction _hb_buffer_set_flags _hb_buffer_set_language _hb_buffer_set_script +_hb_buffer_set_message_func +_hb_buffer_serialize_glyphs _hb_face_create _hb_face_collect_unicodes _hb_face_destroy @@ -25,17 +28,22 @@ _hb_font_destroy _hb_font_glyph_to_string _hb_font_set_scale _hb_font_set_variations +_hb_font_draw_glyph +_hb_draw_funcs_create +_hb_draw_funcs_set_move_to_func +_hb_draw_funcs_set_line_to_func +_hb_draw_funcs_set_quadratic_to_func +_hb_draw_funcs_set_cubic_to_func +_hb_draw_funcs_set_close_path_func _hb_glyph_info_get_glyph_flags _hb_language_from_string _hb_ot_var_get_axis_infos _hb_script_from_string +_hb_feature_from_string _hb_set_create _hb_set_destroy _hb_set_get_population _hb_set_next_many _hb_shape -_hbjs_glyph_svg -_hbjs_shape_with_trace _malloc _free -_free_ptr diff --git a/hbjs.cc b/hbjs.cc deleted file mode 100644 index 8a3255a..0000000 --- a/hbjs.cc +++ /dev/null @@ -1,261 +0,0 @@ -#include "harfbuzz/src/harfbuzz.cc" - -HB_BEGIN_DECLS - -int -hbjs_glyph_svg (hb_font_t *font, hb_codepoint_t glyph, char *buf, unsigned buf_size); - -unsigned -hbjs_shape_with_trace (hb_font_t *font, hb_buffer_t* buf, - char* featurestring, - unsigned int stop_at, unsigned int stop_phase, - char *outbuf, unsigned buf_size); - -void *free_ptr(void); - -HB_END_DECLS - - -void *free_ptr(void) { return (void *) free; } - -enum { - HB_SHAPE_DONT_STOP, - HB_SHAPE_GSUB_PHASE, - HB_SHAPE_GPOS_PHASE -}; - -struct user_data_t { - user_data_t(char *str_, - unsigned size_, - unsigned stop_at_ = 0, - unsigned stop_phase_ = 0) - : str(str_) - , size(size_) - , stop_at(stop_at_) - , stop_phase(stop_phase_) - {} - char *str = nullptr; - unsigned size = 0; - unsigned consumed = 0; - hb_bool_t failure = false; - unsigned stop_at = 0; - unsigned stop_phase = 0; - hb_bool_t stopping = false; - unsigned current_phase = 0; -}; - - -static void -_user_data_printf (user_data_t *data, const char *format, ...) -{ -#define BUFSIZE 1000 - char buf[BUFSIZE]; - int len; - va_list va; - - if (!data || data->failure) - return; - - va_start(va, format); - len = vsnprintf(buf, BUFSIZE, format, va); - va_end(va); - - if (data->consumed + len >= data->size || len < 0 || len > BUFSIZE) - { - data->failure = true; - return; - } - - memcpy (data->str + data->consumed, buf, len); - data->consumed += len; -#undef BUFSIZE -} - -static void -move_to (hb_draw_funcs_t *dfuncs, user_data_t *draw_data, hb_draw_state_t *, - float to_x, float to_y, - void *) -{ - _user_data_printf (draw_data, "M%g,%g", (double)to_x, (double)to_y); -} - -static void -line_to (hb_draw_funcs_t *dfuncs, user_data_t *draw_data, hb_draw_state_t *, - float to_x, float to_y, - void *) -{ - _user_data_printf (draw_data, "L%g,%g", (double)to_x, (double)to_y); -} - -static void -quadratic_to (hb_draw_funcs_t *dfuncs, user_data_t *draw_data, hb_draw_state_t *, - float control_x, float control_y, - float to_x, float to_y, - void *) -{ - _user_data_printf (draw_data, "Q%g,%g %g,%g", - (double)control_x, - (double)control_y, - (double)to_x, - (double)to_y); -} - -static void -cubic_to (hb_draw_funcs_t *dfuncs, user_data_t *draw_data, hb_draw_state_t *, - float control1_x, float control1_y, - float control2_x, float control2_y, - float to_x, float to_y, - void *) -{ - _user_data_printf (draw_data, "C%g,%g %g,%g %g,%g", - (double)control1_x, - (double)control1_y, - (double)control2_x, - (double)control2_y, - (double)to_x, - (double)to_y); -} - -static void -close_path (hb_draw_funcs_t *dfuncs, user_data_t *draw_data, hb_draw_state_t *, void *) -{ - _user_data_printf (draw_data, "Z"); -} - -static hb_draw_funcs_t *funcs = 0; - -int -hbjs_glyph_svg (hb_font_t *font, hb_codepoint_t glyph, char *buf, unsigned buf_size) -{ - if (funcs == 0) /* not the best pattern for multi-threaded apps which is not a concern here */ - { - funcs = hb_draw_funcs_create (); /* will be leaked */ - hb_draw_funcs_set_move_to_func (funcs, (hb_draw_move_to_func_t) move_to, nullptr, nullptr); - hb_draw_funcs_set_line_to_func (funcs, (hb_draw_line_to_func_t) line_to, nullptr, nullptr); - hb_draw_funcs_set_quadratic_to_func (funcs, (hb_draw_quadratic_to_func_t) quadratic_to, nullptr, nullptr); - hb_draw_funcs_set_cubic_to_func (funcs, (hb_draw_cubic_to_func_t) cubic_to, nullptr, nullptr); - hb_draw_funcs_set_close_path_func (funcs, (hb_draw_close_path_func_t) close_path, nullptr, nullptr); - } - - user_data_t draw_data(buf, buf_size); - hb_font_draw_glyph (font, glyph, funcs, &draw_data); - if (draw_data.failure) - return -1; - - buf[draw_data.consumed] = '\0'; - return draw_data.consumed; -} - -static hb_bool_t do_trace (hb_buffer_t *buffer, - hb_font_t *font, - const char *message, - user_data_t *user_data) { - unsigned int consumed; - unsigned int num_glyphs = hb_buffer_get_length (buffer); - - if (strncmp(message, "start table GSUB", 16) == 0) { - user_data->current_phase = HB_SHAPE_GSUB_PHASE; - } else if (strncmp(message, "start table GPOS", 16) == 0) { - user_data->current_phase = HB_SHAPE_GPOS_PHASE; - } - - - if (user_data->current_phase != user_data->stop_phase) { - user_data->stopping = false; - } - - // If we overflowed, keep going anyway. - if (user_data->failure) return 1; - - if (user_data->stop_phase != HB_SHAPE_DONT_STOP) { - // Do we need to start stopping? - char buf[12]; - snprintf (buf, 12, "%u ", user_data->stop_at); - if ((user_data->current_phase == user_data->stop_phase) && - (strncmp(message, "end lookup ", 11) == 0) && - (strncmp(message + 11, buf, strlen(buf)) == 0)) { - user_data->stopping = true; - } - } - - // If we need to stop, stop. - if (user_data->stopping) return 0; - - _user_data_printf (user_data, "{\"m\":\"%s\",\"t\":", message); - hb_buffer_serialize_glyphs(buffer, 0, num_glyphs, - user_data->str + user_data->consumed, - user_data->size - user_data->consumed, - &consumed, - font, - HB_BUFFER_SERIALIZE_FORMAT_JSON, - HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES); - user_data->consumed += consumed; - if (hb_buffer_get_content_type(buffer) == HB_BUFFER_CONTENT_TYPE_GLYPHS) { - _user_data_printf (user_data, ", \"glyphs\": true"); - } else { - _user_data_printf (user_data, ", \"glyphs\": false"); - } - _user_data_printf (user_data, "},\n"); - - return 1; -} - -unsigned -hbjs_shape_with_trace (hb_font_t *font, hb_buffer_t* buf, - char* featurestring, - unsigned int stop_at, unsigned int stop_phase, - char *outbuf, unsigned buf_size) { - user_data_t user_data(outbuf, buf_size, stop_at, stop_phase); - - int num_features = 0; - hb_feature_t* features = nullptr; - - if (*featurestring) { - /* count the features first, so we can allocate memory */ - char* p = featurestring; - do { - num_features++; - p = strchr (p, ','); - if (p) - p++; - } while (p); - - features = (hb_feature_t *) calloc (num_features, sizeof (*features)); - - /* now do the actual parsing */ - p = featurestring; - num_features = 0; - while (p && *p) { - char *end = strchr (p, ','); - if (hb_feature_from_string (p, end ? end - p : -1, &features[num_features])) - num_features++; - p = end ? end + 1 : nullptr; - } - } - - hb_buffer_set_message_func (buf, (hb_buffer_message_func_t)do_trace, &user_data, nullptr); - user_data.str[user_data.consumed++] = '['; - hb_shape(font, buf, features, num_features); - - if (user_data.failure) return -1; - - user_data.str[user_data.consumed-2] = ']'; - user_data.str[user_data.consumed-1] = '\0'; - return user_data.consumed; -} - -#ifdef MAIN -#include -int main() { - hb_blob_t *blob = hb_blob_create_from_file ("/home/ebrahim/Desktop/harfbuzzjs/harfbuzz/test/subset/data/fonts/Roboto-Regular.ttf"); - hb_face_t *face = hb_face_create (blob, 0); - hb_blob_destroy (blob); - hb_font_t *font = hb_font_create (face); - hb_face_destroy (face); - char buf[1024]; - buf[0] = '\0'; - printf ("%d %d\n", hb_blob_get_length (blob), hbjs_ot_glyph_svg (font, 0, buf, sizeof (buf))); - puts (buf); - hb_font_destroy (font); -} -#endif diff --git a/hbjs.js b/hbjs.js index 78bdbde..c34eb21 100644 --- a/hbjs.js +++ b/hbjs.js @@ -1,15 +1,22 @@ -function hbjs(instance) { +function hbjs(Module) { 'use strict'; - var exports = instance.exports; - var heapu8 = new Uint8Array(exports.memory.buffer); - var heapu32 = new Uint32Array(exports.memory.buffer); - var heapi32 = new Int32Array(exports.memory.buffer); - var heapf32 = new Float32Array(exports.memory.buffer); + var exports = Module.wasmExports; + var heapu8 = Module.HEAPU8; + var heapu32 = Module.HEAPU32; + var heapi32 = Module.HEAP32; + var heapf32 = Module.HEAPF32; var utf8Decoder = new TextDecoder("utf8"); + let addFunction = Module.addFunction; + + var freeFuncPtr = addFunction(function (ptr) { exports.free(ptr); }, 'vi'); var HB_MEMORY_MODE_WRITABLE = 2; var HB_SET_VALUE_INVALID = -1; + var HB_BUFFER_CONTENT_TYPE_GLYPHS = 2; + var DONT_STOP = 0; + var GSUB_PHASE = 1; + var GPOS_PHASE = 2; function hb_tag(s) { return ( @@ -20,6 +27,9 @@ function hbjs(instance) { ); } + var HB_BUFFER_SERIALIZE_FORMAT_JSON = hb_tag('JSON'); + var HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES = 4; + function _hb_untag(tag) { return [ String.fromCharCode((tag >> 24) & 0xFF), @@ -46,7 +56,7 @@ function hbjs(instance) { function createBlob(blob) { var blobPtr = exports.malloc(blob.byteLength); heapu8.set(new Uint8Array(blob), blobPtr); - var ptr = exports.hb_blob_create(blobPtr, blob.byteLength, HB_MEMORY_MODE_WRITABLE, blobPtr, exports.free_ptr()); + var ptr = exports.hb_blob_create(blobPtr, blob.byteLength, HB_MEMORY_MODE_WRITABLE, blobPtr, freeFuncPtr); return { ptr: ptr, /** @@ -155,8 +165,7 @@ function hbjs(instance) { }; } - var pathBufferSize = 65536; // should be enough for most glyphs - var pathBuffer = exports.malloc(pathBufferSize); // permanently allocated + var pathBuffer = ""; var nameBufferSize = 256; // should be enough for most glyphs var nameBuffer = exports.malloc(nameBufferSize); // permanently allocated @@ -167,14 +176,46 @@ function hbjs(instance) { **/ function createFont(face) { var ptr = exports.hb_font_create(face.ptr); + var drawFuncsPtr = null; /** * Return a glyph as an SVG path string. * @param {number} glyphId ID of the requested glyph in the font. **/ function glyphToPath(glyphId) { - var svgLength = exports.hbjs_glyph_svg(ptr, glyphId, pathBuffer, pathBufferSize); - return svgLength > 0 ? utf8Decoder.decode(heapu8.subarray(pathBuffer, pathBuffer + svgLength)) : ""; + if (!drawFuncsPtr) { + var moveTo = function (dfuncs, draw_data, draw_state, to_x, to_y, user_data) { + pathBuffer += `M${to_x},${to_y}`; + } + var lineTo = function (dfuncs, draw_data, draw_state, to_x, to_y, user_data) { + pathBuffer += `L${to_x},${to_y}`; + } + var cubicTo = function (dfuncs, draw_data, draw_state, c1_x, c1_y, c2_x, c2_y, to_x, to_y, user_data) { + pathBuffer += `C${c1_x},${c1_y} ${c2_x},${c2_y} ${to_x},${to_y}`; + } + var quadTo = function (dfuncs, draw_data, draw_state, c_x, c_y, to_x, to_y, user_data) { + pathBuffer += `Q${c_x},${c_y} ${to_x},${to_y}`; + } + var closePath = function (dfuncs, draw_data, draw_state, user_data) { + pathBuffer += 'Z'; + } + + var moveToPtr = addFunction(moveTo, 'viiiffi'); + var lineToPtr = addFunction(lineTo, 'viiiffi'); + var cubicToPtr = addFunction(cubicTo, 'viiiffffffi'); + var quadToPtr = addFunction(quadTo, 'viiiffffi'); + var closePathPtr = addFunction(closePath, 'viiii'); + drawFuncsPtr = exports.hb_draw_funcs_create(); + exports.hb_draw_funcs_set_move_to_func(drawFuncsPtr, moveToPtr, 0, 0); + exports.hb_draw_funcs_set_line_to_func(drawFuncsPtr, lineToPtr, 0, 0); + exports.hb_draw_funcs_set_cubic_to_func(drawFuncsPtr, cubicToPtr, 0, 0); + exports.hb_draw_funcs_set_quadratic_to_func(drawFuncsPtr, quadToPtr, 0, 0); + exports.hb_draw_funcs_set_close_path_func(drawFuncsPtr, closePathPtr, 0, 0); + } + + pathBuffer = ""; + exports.hb_font_draw_glyph(ptr, glyphId, drawFuncsPtr, 0); + return pathBuffer; } /** @@ -259,7 +300,7 @@ function hbjs(instance) { function createJsString(text) { const ptr = exports.malloc(text.length * 2); - const words = new Uint16Array(exports.memory.buffer, ptr, text.length); + const words = new Uint16Array(Module.wasmMemory.buffer, ptr, text.length); for (let i = 0; i < words.length; ++i) words[i] = text.charCodeAt(i); return { ptr: ptr, @@ -401,10 +442,25 @@ function hbjs(instance) { * @param {object} font: A font returned from `createFont` * @param {object} buffer: A buffer returned from `createBuffer` and suitably * prepared. - * @param {object} features: (Currently unused). + * @param {object} features: A string of comma-separated OpenType features to apply. */ function shape(font, buffer, features) { - exports.hb_shape(font.ptr, buffer.ptr, 0, 0); + var featuresPtr = 0; + var featuresLen = 0; + if (features) { + features = features.split(","); + featuresPtr = exports.malloc(16 * features.length); + features.forEach(function (feature, i) { + var str = createAsciiString(feature); + if (exports.hb_feature_from_string(str.ptr, -1, featuresPtr + featuresLen * 16)) + featuresLen++; + str.free(); + }); + } + + exports.hb_shape(font.ptr, buffer.ptr, featuresPtr, featuresLen); + if (featuresPtr) + exports.free(featuresPtr); } /** @@ -418,22 +474,63 @@ function hbjs(instance) { * @param {object} font: A font returned from `createFont` * @param {object} buffer: A buffer returned from `createBuffer` and suitably * prepared. - * @param {object} features: A dictionary of OpenType features to apply. + * @param {object} features: A string of comma-separated OpenType features to apply. * @param {number} stop_at: A lookup ID at which to terminate shaping. * @param {number} stop_phase: Either 0 (don't terminate shaping), 1 (`stop_at` refers to a lookup ID in the GSUB table), 2 (`stop_at` refers to a lookup ID in the GPOS table). */ - function shapeWithTrace(font, buffer, features, stop_at, stop_phase) { - var bufLen = 1024 * 1024; - var traceBuffer = exports.malloc(bufLen); - var featurestr = createAsciiString(features); - var traceLen = exports.hbjs_shape_with_trace(font.ptr, buffer.ptr, featurestr.ptr, stop_at, stop_phase, traceBuffer, bufLen); - featurestr.free(); - var trace = utf8Decoder.decode(heapu8.subarray(traceBuffer, traceBuffer + traceLen - 1)); - exports.free(traceBuffer); - return JSON.parse(trace); + var trace = []; + var currentPhase = DONT_STOP; + var stopping = false; + var failure = false; + + var traceBufLen = 1024 * 1024; + var traceBufPtr = exports.malloc(traceBufLen); + + var traceFunc = function (bufferPtr, fontPtr, messagePtr, user_data) { + var message = utf8Decoder.decode(heapu8.subarray(messagePtr, heapu8.indexOf(0, messagePtr))); + if (message.startsWith("start table GSUB")) + currentPhase = GSUB_PHASE; + else if (message.startsWith("start table GPOS")) + currentPhase = GPOS_PHASE; + + if (currentPhase != stop_phase) + stopping = false; + + if (failure) + return 1; + + if (stop_phase != DONT_STOP && currentPhase == stop_phase && message.startsWith("end lookup " + stop_at)) + stopping = true; + + if (stopping) + return 0; + + exports.hb_buffer_serialize_glyphs( + bufferPtr, + 0, exports.hb_buffer_get_length(bufferPtr), + traceBufPtr, traceBufLen, 0, + fontPtr, + HB_BUFFER_SERIALIZE_FORMAT_JSON, + HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES); + + trace.push({ + m: message, + t: JSON.parse(utf8Decoder.decode(heapu8.subarray(traceBufPtr, heapu8.indexOf(0, traceBufPtr)))), + glyphs: exports.hb_buffer_get_content_type(bufferPtr) == HB_BUFFER_CONTENT_TYPE_GLYPHS, + }); + + return 1; + } + + var traceFuncPtr = addFunction(traceFunc, 'iiiii'); + exports.hb_buffer_set_message_func(buffer.ptr, traceFuncPtr, 0, 0); + shape(font, buffer, features, 0); + exports.free(traceBufPtr); + + return trace; } return { diff --git a/index.js b/index.js index 3931d54..5a1a2ab 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,8 @@ -var fs = require('fs'); -var path = require('path'); -var hbjs = require('./hbjs.js') +var hbjs = require('./hbjs.js'); +var hb = require('./hb.js'); module.exports = new Promise(function (resolve, reject) { - fs.readFile(path.resolve(__dirname, 'hb.wasm'), function (err, data) { - if (err) { reject(err); return; } - WebAssembly.instantiate(data).then(function (result) { - resolve(hbjs(result.instance)); - }, reject); - }); + hb().then((instance) => { + resolve(hbjs(instance)); + }, reject); }); diff --git a/test/fonts/noto/NotoSans-Regular.otf b/test/fonts/noto/NotoSans-Regular.otf new file mode 100644 index 0000000..a597e91 Binary files /dev/null and b/test/fonts/noto/NotoSans-Regular.otf differ diff --git a/test/index.js b/test/index.js index 0976650..14f45ca 100644 --- a/test/index.js +++ b/test/index.js @@ -22,6 +22,8 @@ describe('Face', function () { const codepoints = [...this.face.collectUnicodes()]; expect(codepoints).to.include('a'.codePointAt(0)); expect(codepoints).not.to.include('ا'.codePointAt(0)); + this.face.destroy(); + this.blob.destroy(); }); it('exposes upem', function () { @@ -79,6 +81,35 @@ describe('Font', function () { const glyphs = this.buffer.json(); expect(glyphs[0].ax).to.equal(526); }); + + it('glyphToPath converts quadratic glyph to path', function () { + this.blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); + this.face = hb.createFace(this.blob); + this.font = hb.createFont(this.face); + const expected21 = 'M520,0L48,0L48,73L235,262Q289,316 326,358Q363,400 382,440.5Q401,481 401,529Q401,\ +588 366,618.5Q331,649 275,649Q223,649 183.5,631Q144,613 103,581L56,640Q98,675 152.5,699.5Q207,724 275,\ +724Q375,724 433,673.5Q491,623 491,534Q491,478 468,429Q445,380 404,332.5Q363,285 308,231L159,84L159,80L520,80L520,0Z'; + expect(this.font.glyphToPath(21)).to.equal(expected21); + const expected22 = 'M493,547Q493,475 453,432.5Q413,390 345,376L345,372Q431,362 473,318Q515,274 515,203Q515,\ +141 486,92.5Q457,44 396.5,17Q336,-10 241,-10Q185,-10 137,-1.5Q89,7 45,29L45,111Q90,89 142,76.5Q194,64 242,64Q338,\ +64 380.5,101.5Q423,139 423,205Q423,272 370.5,301.5Q318,331 223,331L154,331L154,406L224,406Q312,406 357.5,443Q403,\ +480 403,541Q403,593 368,621.5Q333,650 273,650Q215,650 174,633Q133,616 93,590L49,650Q87,680 143.5,702Q200,724 272,\ +724Q384,724 438.5,674Q493,624 493,547Z'; + expect(this.font.glyphToPath(22)).to.equal(expected22); + }); + + it('glyphToPath converts cubic glyph to path', function () { + this.blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.otf'))); + this.face = hb.createFace(this.blob); + this.font = hb.createFont(this.face); + const expected21 = 'M520,0L520,80L159,80L159,84L308,231C418,338 491,422 491,534C491,652 408,724 275,724C184,724 112,\ +687 56,640L103,581C158,624 205,649 275,649C350,649 401,607 401,529C401,432 342,370 235,262L48,73L48,0L520,0Z'; + expect(this.font.glyphToPath(21)).to.equal(expected21); + const expected22 = 'M493,547C493,649 421,724 272,724C176,724 100,690 49,650L93,590C146,625 196,650 273,650C353,\ +650 403,610 403,541C403,460 341,406 224,406L154,406L154,331L223,331C349,331 423,294 423,205C423,117 370,64 242,64C178,\ +64 105,81 45,111L45,29C104,0 166,-10 241,-10C430,-10 515,78 515,203C515,297 459,358 345,372L345,376C435,394 493,451 493,547Z'; + expect(this.font.glyphToPath(22)).to.equal(expected22); + }); }); describe('Buffer', function () { @@ -161,7 +192,7 @@ describe('shape', function () { this.buffer = hb.createBuffer(); this.buffer.addText('abc'); this.buffer.guessSegmentProperties(); - const result = hb.shapeWithTrace(this.font, this.buffer, 0, 0) + const result = hb.shapeWithTrace(this.font, this.buffer, "", 0, 0) expect(result).to.have.lengthOf(42); expect(result[0]).to.deep.equal({ "m": "start table GSUB script tag 'latn'", @@ -182,4 +213,37 @@ describe('shape', function () { ], }); }); + + it('shape with tracing and features', function () { + this.blob = hb.createBlob(fs.readFileSync(path.join(__dirname, 'fonts/noto/NotoSans-Regular.ttf'))); + this.face = hb.createFace(this.blob); + this.font = hb.createFont(this.face); + this.buffer = hb.createBuffer(); + this.buffer.addText('fi AV'); + this.buffer.guessSegmentProperties(); + const result = hb.shapeWithTrace(this.font, this.buffer, "-liga,-kern", 0, 0) + expect(result).to.have.lengthOf(29); + expect(result[0]).to.deep.equal({ + "m": "start table GSUB script tag 'latn'", + "glyphs": true, + "t": [ + {cl: 0, g: 73}, + {cl: 1, g: 76}, + {cl: 2, g: 3}, + {cl: 3, g: 36}, + {cl: 4, g: 57}, + ], + }); + expect(result[28]).to.deep.equal({ + "m": "end table GPOS script tag 'latn'", + "glyphs": true, + "t": [ + {cl: 0, g: 73, ax: 344, ay: 0, dx: 0, dy: 0}, + {cl: 1, g: 76, ax: 258, ay: 0, dx: 0, dy: 0}, + {cl: 2, g: 3, ax: 260, ay: 0, dx: 0, dy: 0}, + {cl: 3, g: 36, ax: 639, ay: 0, dx: 0, dy: 0}, + {cl: 4, g: 57, ax: 600, ay: 0, dx: 0, dy: 0}, + ], + }); + }); });