Skip to content

Commit

Permalink
[harfbuzzjs] update to track branch of pull harfbuzz/harfbuzzjs#97 as…
Browse files Browse the repository at this point in the history
… it adds support for features to shape.
  • Loading branch information
graphicore committed Mar 26, 2024
1 parent 7253ee0 commit 55bb183
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 55 deletions.
8 changes: 7 additions & 1 deletion lib/js/vendor/harfbuzzjs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ if [ -d harfbuzzjs ]; then
cd harfbuzzjs
git pull --recurse-submodules --rebase
else
git clone --recursive --depth 1 git@github.com:harfbuzz/harfbuzzjs.git
# use js-callbacks as branch as it has just the features argument
# of shape implemented. when the PR is through, this can fetch master
# again ...
# https://github.com/harfbuzz/harfbuzzjs/pull/97
git clone -b js-callbacks --single-branch --recursive --depth 1 git@github.com:harfbuzz/harfbuzzjs.git
cd harfbuzzjs
fi

Expand All @@ -22,3 +26,5 @@ echo '});' >> hbjs.js
cat .build/harfbuzzjs/hbjs.js > hbjs.mjs
echo 'export default hbjs;' >> hbjs.mjs

cat .build/harfbuzzjs/hb.js > hb.mjs
echo 'export default Module;' >> hb.mjs
13 changes: 7 additions & 6 deletions lib/js/vendor/harfbuzzjs/harfbuzz.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
/* jshint esversion:6, browser: true */
import hbjs from './hbjs.mjs';
import hb from './hb.mjs';

// no good way to load wasm from within a module yet.
const wasmURL = new URL('hb.wasm', import.meta.url);

export default async function getHarfbuzz(){
let result = await fetch(wasmURL)
, moduleData = await result.arrayBuffer()
, wasm = await WebAssembly.instantiate(moduleData)
;
export default async function getHarfbuzz() {
function locateFile(/*path, prefix*/) {
return wasmURL.toString();
}
const Module = await hb({locateFile});
// Seems like no longer required, since compiled with em++
// wasm.instance.exports.memory.grow(400); // each page is 64kb in size
return hbjs(wasm.instance);
return {hbjs: hbjs(Module), Module};
}
19 changes: 19 additions & 0 deletions lib/js/vendor/harfbuzzjs/hb.mjs

Large diffs are not rendered by default.

Binary file modified lib/js/vendor/harfbuzzjs/hb.wasm
Binary file not shown.
145 changes: 121 additions & 24 deletions lib/js/vendor/harfbuzzjs/hbjs.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
define(function(require, exports, module){
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 (
Expand All @@ -21,6 +28,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),
Expand All @@ -47,7 +57,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,
/**
Expand Down Expand Up @@ -156,8 +166,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
Expand All @@ -168,14 +177,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;
}

/**
Expand Down Expand Up @@ -260,7 +301,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,
Expand Down Expand Up @@ -402,10 +443,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);
}

/**
Expand All @@ -419,22 +475,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 {
Expand Down
Loading

0 comments on commit 55bb183

Please sign in to comment.