diff options
Diffstat (limited to 'Build/source/libs/harfbuzz/harfbuzz-src/src/hb-wasm-shape.cc')
-rw-r--r-- | Build/source/libs/harfbuzz/harfbuzz-src/src/hb-wasm-shape.cc | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/Build/source/libs/harfbuzz/harfbuzz-src/src/hb-wasm-shape.cc b/Build/source/libs/harfbuzz/harfbuzz-src/src/hb-wasm-shape.cc new file mode 100644 index 00000000000..79442e99ed7 --- /dev/null +++ b/Build/source/libs/harfbuzz/harfbuzz-src/src/hb-wasm-shape.cc @@ -0,0 +1,470 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#undef HB_DEBUG_WASM +#define HB_DEBUG_WASM 1 + +#include "hb-shaper-impl.hh" + +#ifdef HAVE_WASM + +/* Compile wasm-micro-runtime with: + * + * $ cmake -DWAMR_BUILD_MULTI_MODULE=1 -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_JIT=1 + * $ make + * + * If you manage to build a wasm shared module successfully and want to use it, + * do the following: + * + * - Add -DWAMR_BUILD_MULTI_MODULE=1 to your cmake build for wasm-micro-runtime, + * + * - Remove the #define HB_WASM_NO_MODULES line below, + * + * - Install your shared module with name ending in .wasm in + * $(prefix)/$(libdir)/harfbuzz/wasm/ + * + * - Build your font's wasm code importing the shared modules with the desired + * name. This can be done eg.: __attribute__((import_module("graphite2"))) + * before each symbol in the the shared-module's headers. + * + * - Try shaping your font and hope for the best... + * + * I haven't been able to get this to work since emcc's support for shared libraries + * requires support from the host that seems to be missing from wasm-micro-runtime? + */ + +#include "hb-wasm-api.hh" +#include "hb-wasm-api-list.hh" + +#ifndef HB_WASM_NO_MODULES +#define HB_WASM_NO_MODULES +#endif + + +#ifndef HB_WASM_NO_MODULES +static bool HB_UNUSED +_hb_wasm_module_reader (const char *module_name, + uint8_t **p_buffer, uint32_t *p_size) +{ + char path[sizeof (HB_WASM_MODULE_DIR) + 64] = HB_WASM_MODULE_DIR "/"; + strncat (path, module_name, sizeof (path) - sizeof (HB_WASM_MODULE_DIR) - 16); + strncat (path, ".wasm", 6); + + auto *blob = hb_blob_create_from_file (path); + + unsigned length; + auto *data = hb_blob_get_data (blob, &length); + + *p_buffer = (uint8_t *) hb_malloc (length); + + if (length && !p_buffer) + return false; + + memcpy (*p_buffer, data, length); + *p_size = length; + + hb_blob_destroy (blob); + + return true; +} + +static void HB_UNUSED +_hb_wasm_module_destroyer (uint8_t *buffer, uint32_t size) +{ + hb_free (buffer); +} +#endif + +/* + * shaper face data + */ + +#define HB_WASM_TAG_WASM HB_TAG('W','a','s','m') + +struct hb_wasm_shape_plan_t { + wasm_module_inst_t module_inst; + wasm_exec_env_t exec_env; + ptr_d(void, wasm_shape_plan); +}; + +struct hb_wasm_face_data_t { + hb_blob_t *wasm_blob; + wasm_module_t wasm_module; + mutable hb_atomic_ptr_t<hb_wasm_shape_plan_t> plan; +}; + +static bool +_hb_wasm_init () +{ + /* XXX + * + * Umm. Make this threadsafe. How?! + * It's clunky that we can't allocate a static mutex. + * So we have to first allocate one on the heap atomically... + * + * Do we also need to lock around module creation? + * + * Also, wasm-micro-runtime uses a singleton instance. So if + * another library or client uses it, all bets are off. :-( + * If nothing else, around HB_REF2OBJ(). + */ + + static bool initialized; + if (initialized) + return true; + + RuntimeInitArgs init_args; + hb_memset (&init_args, 0, sizeof (RuntimeInitArgs)); + + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = (void *) hb_malloc; + init_args.mem_alloc_option.allocator.realloc_func = (void *) hb_realloc; + init_args.mem_alloc_option.allocator.free_func = (void *) hb_free; + + // Native symbols need below registration phase + init_args.n_native_symbols = ARRAY_LENGTH (_hb_wasm_native_symbols); + init_args.native_module_name = "env"; + init_args.native_symbols = _hb_wasm_native_symbols; + + if (unlikely (!wasm_runtime_full_init (&init_args))) + { + DEBUG_MSG (WASM, nullptr, "Init runtime environment failed."); + return false; + } + +#ifndef HB_WASM_NO_MODULES + wasm_runtime_set_module_reader (_hb_wasm_module_reader, + _hb_wasm_module_destroyer); +#endif + + initialized = true; + return true; +} + +hb_wasm_face_data_t * +_hb_wasm_shaper_face_data_create (hb_face_t *face) +{ + char error[128]; + hb_wasm_face_data_t *data = nullptr; + hb_blob_t *wasm_blob = nullptr; + wasm_module_t wasm_module = nullptr; + + wasm_blob = hb_face_reference_table (face, HB_WASM_TAG_WASM); + unsigned length = hb_blob_get_length (wasm_blob); + if (!length) + goto fail; + + if (!_hb_wasm_init ()) + goto fail; + + wasm_module = wasm_runtime_load ((uint8_t *) hb_blob_get_data_writable (wasm_blob, nullptr), + length, error, sizeof (error)); + if (unlikely (!wasm_module)) + { + DEBUG_MSG (WASM, nullptr, "Load wasm module failed: %s", error); + goto fail; + } + + data = (hb_wasm_face_data_t *) hb_calloc (1, sizeof (hb_wasm_face_data_t)); + if (unlikely (!data)) + goto fail; + + data->wasm_blob = wasm_blob; + data->wasm_module = wasm_module; + + return data; + +fail: + if (wasm_module) + wasm_runtime_unload (wasm_module); + hb_blob_destroy (wasm_blob); + hb_free (data); + return nullptr; +} + +static hb_wasm_shape_plan_t * +acquire_shape_plan (hb_face_t *face, + const hb_wasm_face_data_t *face_data) +{ + char error[128]; + + /* Fetch cached one if available. */ + hb_wasm_shape_plan_t *plan = face_data->plan.get_acquire (); + if (likely (plan && face_data->plan.cmpexch (plan, nullptr))) + return plan; + + plan = (hb_wasm_shape_plan_t *) hb_calloc (1, sizeof (hb_wasm_shape_plan_t)); + + wasm_module_inst_t module_inst = nullptr; + wasm_exec_env_t exec_env = nullptr; + wasm_function_inst_t func = nullptr; + + constexpr uint32_t stack_size = 32 * 1024, heap_size = 2 * 1024 * 1024; + + module_inst = plan->module_inst = wasm_runtime_instantiate (face_data->wasm_module, + stack_size, heap_size, + error, sizeof (error)); + if (unlikely (!module_inst)) + { + DEBUG_MSG (WASM, face_data, "Create wasm module instance failed: %s", error); + goto fail; + } + + exec_env = plan->exec_env = wasm_runtime_create_exec_env (module_inst, + stack_size); + if (unlikely (!exec_env)) { + DEBUG_MSG (WASM, face_data, "Create wasm execution environment failed."); + goto fail; + } + + func = wasm_runtime_lookup_function (module_inst, "shape_plan_create", nullptr); + if (func) + { + wasm_val_t results[1]; + wasm_val_t arguments[1]; + + HB_OBJ2REF (face); + if (unlikely (!faceref)) + { + DEBUG_MSG (WASM, face_data, "Failed to register face object."); + goto fail; + } + + results[0].kind = WASM_I32; + arguments[0].kind = WASM_I32; + arguments[0].of.i32 = faceref; + bool ret = wasm_runtime_call_wasm_a (exec_env, func, + ARRAY_LENGTH (results), results, + ARRAY_LENGTH (arguments), arguments); + + if (unlikely (!ret)) + { + DEBUG_MSG (WASM, module_inst, "Calling shape_plan_create() failed: %s", + wasm_runtime_get_exception (module_inst)); + goto fail; + } + plan->wasm_shape_planptr = results[0].of.i32; + } + + return plan; + +fail: + + if (exec_env) + wasm_runtime_destroy_exec_env (exec_env); + if (module_inst) + wasm_runtime_deinstantiate (module_inst); + hb_free (plan); + return nullptr; +} + +static void +release_shape_plan (const hb_wasm_face_data_t *face_data, + hb_wasm_shape_plan_t *plan, + bool cache = false) +{ + if (cache && face_data->plan.cmpexch (nullptr, plan)) + return; + + auto *module_inst = plan->module_inst; + auto *exec_env = plan->exec_env; + + /* Is there even any point to having a shape_plan_destroy function + * and calling it? */ + if (plan->wasm_shape_planptr) + { + + auto *func = wasm_runtime_lookup_function (module_inst, "shape_plan_destroy", nullptr); + if (func) + { + wasm_val_t arguments[1]; + + arguments[0].kind = WASM_I32; + arguments[0].of.i32 = plan->wasm_shape_planptr; + bool ret = wasm_runtime_call_wasm_a (exec_env, func, + 0, nullptr, + ARRAY_LENGTH (arguments), arguments); + + if (unlikely (!ret)) + { + DEBUG_MSG (WASM, module_inst, "Calling shape_plan_destroy() failed: %s", + wasm_runtime_get_exception (module_inst)); + } + } + } + + wasm_runtime_destroy_exec_env (exec_env); + wasm_runtime_deinstantiate (module_inst); + hb_free (plan); +} + +void +_hb_wasm_shaper_face_data_destroy (hb_wasm_face_data_t *data) +{ + if (data->plan.get_relaxed ()) + release_shape_plan (data, data->plan); + wasm_runtime_unload (data->wasm_module); + hb_blob_destroy (data->wasm_blob); + hb_free (data); +} + + +/* + * shaper font data + */ + +struct hb_wasm_font_data_t {}; + +hb_wasm_font_data_t * +_hb_wasm_shaper_font_data_create (hb_font_t *font HB_UNUSED) +{ + return (hb_wasm_font_data_t *) HB_SHAPER_DATA_SUCCEEDED; +} + +void +_hb_wasm_shaper_font_data_destroy (hb_wasm_font_data_t *data HB_UNUSED) +{ +} + + +/* + * shaper + */ + +hb_bool_t +_hb_wasm_shape (hb_shape_plan_t *shape_plan, + hb_font_t *font, + hb_buffer_t *buffer, + const hb_feature_t *features, + unsigned int num_features) +{ + if (unlikely (buffer->in_error ())) + return false; + + bool ret = true; + hb_face_t *face = font->face; + const hb_wasm_face_data_t *face_data = face->data.wasm; + + bool retried = false; + if (0) + { +retry: + DEBUG_MSG (WASM, font, "Retrying..."); + } + + wasm_function_inst_t func = nullptr; + + hb_wasm_shape_plan_t *plan = acquire_shape_plan (face, face_data); + if (unlikely (!plan)) + { + DEBUG_MSG (WASM, face_data, "Acquiring shape-plan failed."); + return false; + } + + auto *module_inst = plan->module_inst; + auto *exec_env = plan->exec_env; + + HB_OBJ2REF (font); + HB_OBJ2REF (buffer); + if (unlikely (!fontref || !bufferref)) + { + DEBUG_MSG (WASM, module_inst, "Failed to register objects."); + goto fail; + } + + func = wasm_runtime_lookup_function (module_inst, "shape", nullptr); + if (unlikely (!func)) + { + DEBUG_MSG (WASM, module_inst, "Shape function not found."); + goto fail; + } + + wasm_val_t results[1]; + wasm_val_t arguments[5]; + + results[0].kind = WASM_I32; + arguments[0].kind = WASM_I32; + arguments[0].of.i32 = plan->wasm_shape_planptr; + arguments[1].kind = WASM_I32; + arguments[1].of.i32 = fontref; + arguments[2].kind = WASM_I32; + arguments[2].of.i32 = bufferref; + arguments[3].kind = WASM_I32; + arguments[3].of.i32 = num_features ? wasm_runtime_module_dup_data (module_inst, + (const char *) features, + num_features * sizeof (features[0])) : 0; + arguments[4].kind = WASM_I32; + arguments[4].of.i32 = num_features; + + ret = wasm_runtime_call_wasm_a (exec_env, func, + ARRAY_LENGTH (results), results, + ARRAY_LENGTH (arguments), arguments); + + if (num_features) + wasm_runtime_module_free (module_inst, arguments[2].of.i32); + + if (unlikely (!ret || !results[0].of.i32)) + { + DEBUG_MSG (WASM, module_inst, "Calling shape() failed: %s", + wasm_runtime_get_exception (module_inst)); + if (!buffer->ensure_unicode ()) + { + DEBUG_MSG (WASM, font, "Shape failed but buffer is not in Unicode; failing..."); + goto fail; + } + if (retried) + { + DEBUG_MSG (WASM, font, "Giving up..."); + goto fail; + } + buffer->successful = true; + retried = true; + release_shape_plan (face_data, plan); + plan = nullptr; + goto retry; + } + + /* TODO Regularize clusters according to direction & cluster level, + * such that client doesn't crash with unmet expectations. */ + + if (!results[0].of.i32) + { +fail: + ret = false; + } + + release_shape_plan (face_data, plan, ret); + + if (ret) + { + buffer->clear_glyph_flags (); + buffer->unsafe_to_break (); + } + + return ret; +} + +#endif |