Compare commits

...

147 Commits
main ... wasm

Author SHA1 Message Date
Behdad Esfahbod 2663a9b6f9 [wasm] Rename a couple APIs 2023-03-31 17:38:23 -06:00
Behdad Esfahbod d2fb583a5a [wasm-graphite] Fix advance signedness 2023-03-31 17:33:45 -06:00
Behdad Esfahbod f9dd402ef8 [wasm] Fix header 2023-03-31 17:33:45 -06:00
Simon Cozens 85a1fdd93f [wasm] get/set font variation parameters 2023-03-31 17:33:45 -06:00
Simon Cozens 0bfad127c3 [wasm] Make _hb_wasm_module_reader conditional
Or else uharfbuzz doesn't build.
2023-03-31 17:33:45 -06:00
Simon Cozens 3fc48d4ada [wasm] [experimental] wrap hb_face_create and hb_create_font 2023-03-31 17:33:45 -06:00
Simon Cozens 0a16c60b42 [wasm-rust] Derive some friendly traits 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 6ff994f31d [wasm-api-shape] Avoid a couple of crashes 2023-03-31 17:33:45 -06:00
Behdad Esfahbod b6c1814410 [wasm] Comment 2023-03-31 17:33:45 -06:00
Simon Cozens e78d8653ce [wasm-rust] panic if buffer_set_contents fails 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 74a2f338c6 [wasm-shape] Don't crash if font is bad 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 7df9b3dd89 [wasm-api-list] Add TODO 2023-03-31 17:33:45 -06:00
Behdad Esfahbod f5a0bd223b [wasm-shape] Comment re thread-safety 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 7e5064ac1b [wasm-shape] Comment re thread-safety 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 5235ee68ad [wasm-shape] Remove explicit running-mode setting
It does it automatically.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod e89415b5b9 [wasm-shape] Print another error message 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 6aea77c643 [wasm-shape] Add (disabled) module support 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 142ceaf246 [wasm-shape] Print module instantiation error 2023-03-31 17:33:45 -06:00
Simon Cozens 7e397d8695 [wasm-rust] Fix shape parameters 2023-03-31 17:33:45 -06:00
Simon Cozens d942f72c2a [wasm-rust] Fix path 2023-03-31 17:33:45 -06:00
Simon Cozens 52b11546c8 [wasm-rust] Optional kurbo dependency 2023-03-31 17:33:45 -06:00
Simon Cozens 0c90555e59 [wasm] Fix Rust docs 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 4f537df67d [wasm] Disable check-libstdc++ test
Since libiwasm.so links to it.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod 2482bb120b [wasm-shape] Add XXX item 2023-03-31 17:33:45 -06:00
Behdad Esfahbod a53690a937 [wasm-shape] Minor rename 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 3226b4342b [wasm-shape] Use hb allocators 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 4096115b48 [wasm-graphite] Export malloc/free
Makes the wasm-micro-runtime use these instead of internal heap.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod 7b1c5e86ae [wasm-shape] Minor conditionalize allocation 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 196c6b6c1f [wasm-buffer] Fix buffer_contents_realloc 2023-03-31 17:33:45 -06:00
Behdad Esfahbod b9d4758bf9 [wasm/graphite] Add commented out allocator export 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 3c59943433 [wasm/graphite] Remove stale comment 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 6311b72fcc [wasm-shape] Shuffle code around 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 9b66e45481 [wasm/graphite] Remove unused variable
Wasm cannot export those?
2023-03-31 17:33:45 -06:00
Behdad Esfahbod c5a88a068b [wasm-api] Minor cleanup 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 8215e70632 [wasm-graphite] Memory hygiene 2023-03-31 17:33:45 -06:00
Behdad Esfahbod faaae04359 [wasm-shape] Minor 2023-03-31 17:33:45 -06:00
Behdad Esfahbod d5d8fc046f [wasm/graphite] Include standard headers
Since we use emcc here.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod 1f86890b75 [wasm/graphite] Typo 2023-03-31 17:33:45 -06:00
Behdad Esfahbod f95c2cc6d5 [wasm] Infra for iwasm llvm build 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 65f7bac73c Revert "[wasm-shape] No need to set default runnint mode"
This reverts commit fa484fac08a868e885b3252522668824f0f0fe71.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod d894a10699 [wasm-shape] Debug message 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 91eb2f49db [wasm-api-buffer] Minor variable 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 4260de12c1 [wasm] Add HB_ARRAY_APP2NATIVE 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 1537e252ba [wasm] Minor blob validation 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 918df8ccaf [wasm-api] Add glyph_outline_free 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 7fff4a19ad [wasm-api] Reuse allocation in font_copy_glyph_outline 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 41362cc339 [wasm] Simplify memory cleaning 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 6746ca4ae2 [wasm] Minor remove undefine function 2023-03-31 17:33:45 -06:00
Behdad Esfahbod b08026187a [wasm-api] Memory house-keeping 2023-03-31 17:33:45 -06:00
Behdad Esfahbod f2d227ad9f [wasm-api] Respect existing blob allocation in face_copy_blob 2023-03-31 17:33:45 -06:00
Behdad Esfahbod e7540043de [wasm-api] Make buffer_copy_contents reuse contents 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 65966e0c3d [wasm-api] Add font_copy_glyph_outline 2023-03-31 17:33:45 -06:00
Simon Cozens 92a57b4b4a [wasm-rust] update font_copy_table API 2023-03-31 17:33:45 -06:00
Simon Cozens db789eacb4 [wasm-rust] Docs and API update 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 0d237d062e [wasm-shape] No need to set default runnint mode
It's detected automatically apparently.
2023-03-31 17:33:45 -06:00
Behdad Esfahbod 2004528cf8 [wasm-api] Change face_copy_table to return success 2023-03-31 17:33:45 -06:00
Simon Cozens 83b9c34f0b [wasm] Add rust example 2023-03-31 17:33:45 -06:00
Simon Cozens 840b5dff73 [wasm] Improve Rust docs, refer to new crate 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 28a7c1f932 [wasm-api] Rename face_reference_table to face_copy_table 2023-03-31 17:33:45 -06:00
Behdad Esfahbod b5b577f29f [wasm-api] Make buffer_copy_contents return success 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 1023a80d39 [wasm] Fix up samples 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 4ad659a608 [wasm] Move wasm-graphite 2023-03-31 17:33:45 -06:00
Simon Cozens 514a8d58d8 [wasm-api] Add ergonomic Rust interface 2023-03-31 17:33:45 -06:00
Simon Cozens 74deaa9e78 [wasm-api] Restructure samples/libraries 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 4bdfaeecef [wasm-shape] Set glyph flags 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 77f0f3c11a [wasm-api] Make buffer_copy_contents return zero length on mem fail 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 16ecb96922 [wasm-api] Return success from buffer_contents_realloc 2023-03-31 17:33:45 -06:00
Behdad Esfahbod 2568890d15 [wasm-shape] Retry shaping if out-of-memory 2023-03-31 17:33:45 -06:00
Behdad Esfahbod cb382e489d [wasm-shape] Cache wasm-shape-plan 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 73de7d4d05 [wasm-api] Add shape_plan 2023-03-31 17:33:44 -06:00
Behdad Esfahbod a267249930 [wasm] Typo 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 99d2dab30f [wasm] Try at autotools build 2023-03-31 17:33:44 -06:00
Behdad Esfahbod ed39e07661 [wasm] Fix docs 2023-03-31 17:33:44 -06:00
Behdad Esfahbod c5af08c0dc [wasm] Add to harfbuzz.cc and hb-features 2023-03-31 17:33:44 -06:00
Behdad Esfahbod aa8f9eed63 [wasm-shape] Use JIT running-modes if available
Build wasm-micro-runtime with:

$ cmake . -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_JIT=1

or:

$ cmake . -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_JIT=1

The latter needs llvm and is harder to get working. Still trying.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 8d960dfe68 [wasm-sample] Remove Wasm table from test.ttf again
Was added by mistake.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 1c6d640e1f [wasm-shaper] Whitespace 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 9f8ad3928a [wasm-api] Bind shaper features 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 2327fe9d8a [hb-wasm] Remove TODO 2023-03-31 17:33:44 -06:00
Behdad Esfahbod b130b2b331 [graphite] Simplify direction handling 2023-03-31 17:33:44 -06:00
Behdad Esfahbod ec3270c7bb [wasm-graphite] Ensure native direction here too
Binds buffer_get_script and script_get_horizontal_direction.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod cbc71c56bc [graphite] Ensure native direction
Mirrored characters come out wrong. Oh well. Better than before though.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 39f8703df1 [wasm-api] Match interface and implementation signatures 2023-03-31 17:33:44 -06:00
Behdad Esfahbod fea3ffe031 [wasm-api] Beautify internal API 2023-03-31 17:33:44 -06:00
Behdad Esfahbod fe557e2f21 [wasm-api] Bind font_get_glyph_extents
Untested.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod b3b6e8da86 [wasm-api] Bind buffer_reverse 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 863ec70e12 [wasm-shape] Add TODO items 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 1acff90b03 [wasm-api] Bind font_glyph_to_string 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 7537d48f08 [wasm] Remove a few lingering hb_ 2023-03-31 17:33:44 -06:00
Behdad Esfahbod d7f76f30b0 [wasm-graphite] Memory cleanup 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 5738851b1c [wasm-api] Disallow "wasm" shaper in shape_with 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 2bde2f66f1 [wasm-api] Bind shape_with 2023-03-31 17:33:44 -06:00
Behdad Esfahbod a08dbf41cd [wasm-api] Bind buffer_reverse_clusters 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 0a51ed31b0 [wasm-api] Bind buffer_get_direction 2023-03-31 17:33:44 -06:00
Behdad Esfahbod a5c844a1de [wasm] Rename macro 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 23b58b5667 [wasm-api] Bind font_get_scale 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 149199ee26 [wasm-api] Bind face_get_upem 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 2d295183b8 [wasm-api] Add buffer_contents_realloc 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 07ece17495 [wasm-graphite] Initial code 2023-03-31 17:33:44 -06:00
Behdad Esfahbod ae981eec8e [graphite] Remove script handling
https://github.com/harfbuzz/harfbuzz/issues/3439#issuecomment-1442650148
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 5ab7f7a7d4 [wasm-sample] Make addTable take args 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 551528a6e6 [wasm-sample] Remove unused prototype 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 926f8a326e [wasm-sample] Actually shape text! 2023-03-31 17:33:44 -06:00
Behdad Esfahbod c1dc112121 [wasm-api] Bind buffer_set_contents 2023-03-31 17:33:44 -06:00
Simon Cozens e0fec1dda0 [wasm-api] Wrap some of hb-font 2023-03-31 17:33:44 -06:00
Behdad Esfahbod af1f41a43e [wasm-api] Clear structs in _free() 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 099a0150e1 [wasm] Add HB_STRUCT_TYPE 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 851ef1380a [wasm-api] Add buffer-contents-free 2023-03-31 17:33:44 -06:00
Behdad Esfahbod d38f02ab30 [wasm-api] Make ref types actual pointers on the wasm side 2023-03-31 17:33:44 -06:00
Behdad Esfahbod cbd5c554fb [wasm-api] Add blob_free 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 3bec8dca1c [wasm-sample] Free blob data for now 2023-03-31 17:33:44 -06:00
Behdad Esfahbod d45a13f101 [wasm] Add wasm-sample/ 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 5cecfe8659 [wasm-api] Bind buffer_copy_contents 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 50b22368d0 Revert "Fix function signature"
This reverts commit d70ebf98b0c696f3c66a20b1243cb347e3e3abc8.

Nope. A struct return is not returned that way.
2023-03-31 17:33:44 -06:00
Simon Cozens 743cd2c46e Fix function signature 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 3b88bd9742 [wasm-api] Make ptr_t a pointer on the wasm side 2023-03-31 17:33:44 -06:00
Behdad Esfahbod d7a6671676 [wasm-api] Add debugprint1/2/3/4 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 108995bbc6 [wasm-api] Finish face_reference_table 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 69b1707d82 [wasm] Return empty object when ref2obj fails 2023-03-31 17:33:44 -06:00
Behdad Esfahbod e87b1b3ec3 [wasm-api] Try to add face_reference_table 2023-03-31 17:33:44 -06:00
Behdad Esfahbod e03726d269 [wasm] Ignore API in the docs 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 747dcf561d [wasm] Strong typing for object references
Such that wasm cannot crash us by passing wrong object refs.

https://github.com/bytecodealliance/wasm-micro-runtime/discussions/1987

It still is unsafe if some code in the process other than HarfBuzz
registers refs with wasm-micro-runtime, since wasm_externref_ref2obj()
takes no context variable and looks up refs globally :(.

Maybe I fix that later by keeping a hash table of ref->obj-type instead.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 6b72a18c7b [wasm-api] Rename file 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 42d121ae79 [wasm] Add hb-wasm-api.cc 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 980706441b [wasm] Add hb-wasm-api-list.hh 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 65efad6b59 [wasm] Make debugprint use debug API 2023-03-31 17:33:44 -06:00
Behdad Esfahbod bb8a04cbad [wasm] Remove the print(returnval) and use it to fail shaper 2023-03-31 17:33:44 -06:00
Simon Cozens 11fc83c0ba [Docs] Pass a C string from Rust to HB 2023-03-31 17:33:44 -06:00
Simon Cozens 36dd27bf3f Just printf, don't take length 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 3bc0ecf28c [wasm-api] Add ref_t 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 62f3c7cf67 [wasm-api] Add macros for ref handling 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 4c8a414a10 [wasm-api] Clean up debugprint 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 63904538c8 [wasm-api] Use i32 instead of externref in API spec
Oh well...
2023-03-31 17:33:44 -06:00
Behdad Esfahbod bd28d01a6a [wasm-api] Add HB_WASM_INTERFACE 2023-03-31 17:33:44 -06:00
Simon Cozens ed1a70c7e8 Only export debugprint when HB_DEBUG_WASM 2023-03-31 17:33:44 -06:00
Simon Cozens 89c50b0ccb Fix debugprint format string 2023-03-31 17:33:44 -06:00
Simon Cozens f0200445d0 Initial WASM docs and Rust example 2023-03-31 17:33:44 -06:00
Behdad Esfahbod fd1f7f46f4 [wasm-api] Implement font_get_face 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 9f4dc2e103 [wasm] Bind native API 2023-03-31 17:33:44 -06:00
Behdad Esfahbod e79a7318c4 [wasm] Start adding wasm-api 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 829ba74284 [wasm] Add hb-wasm-api.h 2023-03-31 17:33:44 -06:00
Behdad Esfahbod 11c6d46086 [wasm] Pass font & buff to shape() function 2023-03-31 17:33:44 -06:00
Simon Cozens 05bf984212 Add a simple callback native function 2023-03-31 17:33:44 -06:00
Behdad Esfahbod bdbc1568ba [wasm] Port from c_api to wasm-micro-runtime wasm_runtime API 2023-03-31 17:33:44 -06:00
Behdad Esfahbod db8e51e01b [wasm] More boilerplate 2023-03-31 17:33:44 -06:00
Behdad Esfahbod fcc8be409b [wasm] Add meson build option
Autotools support missing.
2023-03-31 17:33:44 -06:00
Behdad Esfahbod 425fc7f3ee [wasm] Add wasm shaper skeleton 2023-03-31 17:33:44 -06:00
37 changed files with 3006 additions and 23 deletions

View File

@ -417,6 +417,28 @@ AM_CONDITIONAL(HAVE_CORETEXT, $have_coretext)
dnl ===========================================================================
AC_ARG_WITH(wasm,
[AS_HELP_STRING([--with-wasm=@<:@yes/no/auto@:>@],
[Use the wasm-micro-runtime library @<:@default=no@:>@])],,
[with_wasm=no])
have_wasm=false
if test "x$with_wasm" = "xyes" -o "x$with_wasm" = "xauto"; then
AC_CHECK_HEADERS(wasm_export.h, have_wasm=true)
fi
if test "x$with_wasm" = "xyes" -a "x$have_wasm" != "xtrue"; then
AC_MSG_ERROR([wasm support requested but not found])
fi
if $have_wasm; then
WASM_CFLAGS=
WASM_LIBS="-liwasm"
AC_SUBST(WASM_CFLAGS)
AC_SUBST(WASM_LIBS)
AC_DEFINE(HAVE_WASM, 1, [Have wasm-micro-runtime library])
fi
AM_CONDITIONAL(HAVE_WASM, $have_wasm)
dnl ===========================================================================
AC_CONFIG_FILES([
Makefile
src/Makefile
@ -478,6 +500,7 @@ Platform shapers (not normally needed):
DirectWrite: ${have_directwrite}
GDI: ${have_gdi}
Uniscribe: ${have_uniscribe}
WebAssembly: ${have_wasm}
Other features:
Documentation: ${enable_gtk_doc}

View File

@ -197,6 +197,7 @@ HB_HAS_GOBJECT
HB_HAS_GRAPHITE
HB_HAS_ICU
HB_HAS_UNISCRIBE
HB_HAS_WASM
</SECTION>
<SECTION>

View File

@ -41,6 +41,7 @@ ignore_headers = [
'hb-gobject-enums.h',
'hb-gobject-enums-tmp.h',
'hb-gobject-structs.h',
'hb-wasm-api.h',
]
gnome.gtkdoc('harfbuzz',

152
docs/wasm-shaper.md Normal file
View File

@ -0,0 +1,152 @@
# The web assembly shaper
If the standard OpenType shaping engine doesn't give you enough flexibility, Harfbuzz allows you to write your own shaping engine in WebAssembly and embed it into your font! Any font which contains a `Wasm` table will be passed to the WebAssembly shaper.
## How to write a shaping engine in Rust
Here are the steps to create an example shaping engine in Rust: (These examples can also be found in `src/wasm/sample/rust`)
* First, install wasm-pack, which helps us to generate optimized WASM files. It writes some Javascript bridge code that we don't need, but it makes the build and deployment process much easier:
```
$ cargo install wasm-pack
```
* Now let's create a new library:
```
$ cargo new --lib hello-wasm
```
* We need the target to be a dynamic library, and we're going to use `bindgen` to export our Rust function to WASM, so let's put these lines in the `Cargo.toml`. The Harfbuzz sources contain a Rust crate which makes it easy to create the shaper, so we'll specify that as a dependency as well:
```toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
harfbuzz-wasm = { path = "your-harfbuzz-source/src/wasm/rust/harfbuzz-wasm"}
```
*
* And now we'll create our shaper code. In `src/lib.rs`:
```rust
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn shape(_shape_plan:u32, font_ref: u32, buf_ref: u32, _features: u32, _num_features: u32) -> i32 {
1 // success!
}
```
This exports a shaping function which takes four arguments, tokens representing the shaping plan, the font and the buffer, and returns a status value. We can pass these tokens back to Harfbuzz in order to use its native functions on the font and buffer objects. More on native functions later - let's get this shaper compiled and added into a font:
* To compile the shaper, run `wasm-pack build --target nodejs`:
```
INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling hello-wasm v0.1.0 (...)
Finished release [optimized] target(s) in 0.20s
[WARN]: ⚠️ origin crate has no README
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 0.40s
```
You'll find the output WASM file in `pkg/hello_wasm_bg.wasm`
* Now we need to get it into a font.
We provide a utility to do this called `addTable.py` in the `src/` directory:
```
% python3 ~/harfbuzz/src/addTable.py test.ttf test-wasm.ttf pkg/hello_wasm_bg.wasm
```
And now we can run it!
```
% hb-shape test-wasm.ttf abc --shapers=wasm
[cent=0|sterling=1|fraction=2]
```
(The `--shapers=wasm` isn't necessary, as any font with a `Wasm` table will be sent to the WASM shaper if it's enabled, but it proves the point.)
Congratulations! Our shaper did nothing, but in Rust! Now let's do something - it's time for the Hello World of WASM shaping.
* To say hello world, we're going to have to use a native function.
In debugging builds of Harfbuzz, we can print some output from the web assembly module to the host's standard output using the `debug` function. To make this easier, we've got the `harfbuzz-wasm` crate:
```rust
use harfbuzz_wasm::debug;
#[wasm_bindgen]
pub fn shape(_shape_plan:u32, _font_ref: u32, _buf_ref: u32, _features: u32, _num_features: u32) -> i32 {
debug("Hello from Rust!\n");
1
}
```
With this compiled into a WASM module, and installed into our font again, finally our fonts can talk to us!
```
$ hb-shape test-wasm.ttf abc
Hello from Rust!
[cent=0|sterling=1|fraction=2]
```
Now let's start to do some actual, you know, *shaping*. The first thing a shaping engine normally does is (a) map the items in the buffer from Unicode codepoints into glyphs in the font, and (b) set the advance width of the buffer items to the default advance width for those glyphs. We're going to need to interrogate the font for this information, and write back to the buffer. Harfbuzz provides us with opaque pointers to the memory for the font and buffer, but we can turn those into useful Rust structures using the `harfbuzz-wasm` crate again:
```rust
use wasm_bindgen::prelude::*;
use harfbuzz_wasm::{Font, GlyphBuffer};
#[wasm_bindgen]
pub fn shape(_shape_plan:u32, font_ref: u32, buf_ref: u32, _features: u32, _num_features: u32) -> i32 {
let font = Font::from_ref(font_ref);
let mut buffer = GlyphBuffer::from_ref(buf_ref);
for mut item in buffer.glyphs.iter_mut() {
// Map character to glyph
item.codepoint = font.get_glyph(codepoint, 0);
// Set advance width
item.h_advance = font.get_glyph_h_advance(item.codepoint);
}
1
}
```
The `GlyphBuffer`, unlike in Harfbuzz, combines positioning and information in a single structure, to save you having to zip and unzip all the time. It also takes care of marshalling the buffer back to Harfbuzz-land; when a GlyphBuffer is dropped, it writes its contents back through the reference into Harfbuzz's address space. (If you want a different representation of buffer items, you can have one: `GlyphBuffer` is implemented as a `Buffer<Glyph>`, and if you make your own struct which implements the `BufferItem` trait, you can make a buffer out of that instead.)
One easy way to write your own shapers is to make use of OpenType shaping for the majority of your shaping work, and then make changes to the pre-shaped buffer afterwards. You can do this using the `Font.shape_with` method. Run this on a buffer reference, and then construct your `GlyphBuffer` object afterwards:
```rust
use harfbuzz_wasm::{Font, GlyphBuffer};
use tiny_rng::{Rand, Rng};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn shape(_shape_plan:u32, font_ref: u32, buf_ref: u32, _features: u32, _num_features: u32) -> i32 {
let mut rng = Rng::from_seed(123456);
// Use the default OpenType shaper
let font = Font::from_ref(font_ref);
font.shape_with(buf_ref, "ot");
// Now we have a buffer with glyph ids, advance widths etc.
// already filled in.
let mut buffer = GlyphBuffer::from_ref(buf_ref);
for mut item in buffer.glyphs.iter_mut() {
// Randomize it!
item.x_offset = ((rng.rand_u32() as i32) >> 24) - 120;
item.y_offset = ((rng.rand_u32() as i32) >> 24) - 120;
}
1
}
```
See the documentation for the `harfbuzz-wasm` crate for all the other

View File

@ -114,6 +114,9 @@ glib_dep = dependency('glib-2.0', required: get_option('glib'))
gobject_dep = dependency('gobject-2.0', required: get_option('gobject'))
graphite2_dep = dependency('graphite2', required: get_option('graphite2'))
graphite_dep = dependency('graphite2', required: get_option('graphite'))
wasm_dep = cpp.find_library('iwasm', required: get_option('wasm'))
# How to check whether iwasm was built, and hence requires, LLVM?
#llvm_dep = cpp.find_library('LLVM-15', required: get_option('wasm'))
if meson.version().version_compare('>=0.60.0')
# pkg-config: icu-uc, cmake: ICU but with components
@ -229,6 +232,11 @@ if chafa_dep.found()
conf.set('HAVE_CHAFA', 1)
endif
if wasm_dep.found()
conf.set('HAVE_WASM', 1)
conf.set('HB_WASM_MODULE_DIR', '"'+get_option('prefix')+'/'+get_option('libdir')+'/harfbuzz/wasm"')
endif
if graphite2_dep.found() or graphite_dep.found()
conf.set('HAVE_GRAPHITE2', 1)
endif

View File

@ -21,6 +21,8 @@ option('directwrite', type: 'feature', value: 'disabled',
description: 'Enable DirectWrite shaper backend on Windows (experimental)')
option('coretext', type: 'feature', value: 'disabled',
description: 'Enable CoreText shaper backend on macOS')
option('wasm', type: 'feature', value: 'disabled',
description: 'Enable WebAssembly shaper backend')
# Common feature options
option('tests', type: 'feature', value: 'enabled', yield: true,

View File

@ -114,6 +114,16 @@ else
HB_HAS_CORETEXT_DEF = undef HB_HAS_CORETEXT
endif
if HAVE_WASM
HBCFLAGS += $(WASM_CFLAGS)
HBNONPCLIBS += $(WASM_LIBS)
HBSOURCES += $(HB_WASM_sources)
HBHEADERS += $(HB_WASM_headers)
HB_HAS_WASM_DEF = define HB_HAS_WASM 1
else
HB_HAS_WASM_DEF = undef HB_HAS_WASM
endif
BUILT_SOURCES += \
hb-version.h
@ -273,16 +283,17 @@ DISTCLEANFILES += \
hb-features.h: hb-features.h.in $(top_builddir)/config.status
$(AM_V_GEN) $(SED) \
-e 's/mesondefine HB_HAS_CAIRO/$(HB_HAS_CAIRO_DEF)/' \
-e 's/mesondefine HB_HAS_CORETEXT/$(HB_HAS_CORETEXT_DEF)/' \
-e 's/mesondefine HB_HAS_DIRECTWRITE/$(HB_HAS_DIRECTWRITE_DEF)/' \
-e 's/mesondefine HB_HAS_FREETYPE/$(HB_HAS_FREETYPE_DEF)/' \
-e 's/mesondefine HB_HAS_GDI/$(HB_HAS_GDI_DEF)/' \
-e 's/mesondefine HB_HAS_GDI/$(HB_HAS_GDI_DEF)/' \
-e 's/mesondefine HB_HAS_GRAPHITE/$(HB_HAS_GRAPHITE_DEF)/' \
-e 's/mesondefine HB_HAS_GLIB/$(HB_HAS_GLIB_DEF)/' \
-e 's/mesondefine HB_HAS_GOBJECT/$(HB_HAS_GOBJECT_DEF)/' \
-e 's/mesondefine HB_HAS_UNISCRIBE/$(HB_HAS_UNISCRIBE_DEF)/' \
-e 's/mesondefine HB_HAS_DIRECTWRITE/$(HB_HAS_DIRECTWRITE_DEF)/' \
-e 's/mesondefine HB_HAS_CORETEXT/$(HB_HAS_CORETEXT_DEF)/' \
-e 's/mesondefine HB_HAS_GRAPHITE/$(HB_HAS_GRAPHITE_DEF)/' \
-e 's/mesondefine HB_HAS_ICU/$(HB_HAS_ICU_DEF)/' \
-e 's/mesondefine HB_HAS_UNISCRIBE/$(HB_HAS_UNISCRIBE_DEF)/' \
-e 's/mesondefine HB_HAS_WASM/$(HB_HAS_WASM_DEF)/' \
"$<" > "$@" || ($(RM) "$@"; false)

View File

@ -338,10 +338,22 @@ HB_GDI_headers = hb-gdi.h
HB_UNISCRIBE_sources = hb-uniscribe.cc
HB_UNISCRIBE_headers = hb-uniscribe.h
# Sources for libharfbuzz-gobject and libharfbuzz-icu
HB_ICU_sources = hb-icu.cc
HB_ICU_headers = hb-icu.h
HB_WASM_sources = \
hb-wasm-api.cc \
hb-wasm-api.hh \
hb-wasm-api-blob.hh \
hb-wasm-api-buffer.hh \
hb-wasm-api-common.hh \
hb-wasm-api-face.hh \
hb-wasm-api-font.hh \
hb-wasm-api-shape.hh \
hb-wasm-shape.cc \
$(NULL)
HB_WASM_headers = hb-wasm-api.h
# Sources for libharfbuzz-subset
HB_SUBSET_sources = \
hb-number.cc \

16
src/addTable.py Normal file
View File

@ -0,0 +1,16 @@
import sys
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.DefaultTable import DefaultTable
if len(sys.argv) == 1:
print("usage: python addTable.py input.ttf output.ttf Wasm.bin")
sys.exit(1)
font = TTFont(sys.argv[1])
wasm_table = DefaultTable("Wasm")
wasm_table.data = open(sys.argv[3], "rb").read()
font["Wasm"] = wasm_table
font.save(sys.argv[2])

View File

@ -58,3 +58,5 @@
#include "hb-ucd.cc"
#include "hb-unicode.cc"
#include "hb-uniscribe.cc"
#include "hb-wasm-api.cc"
#include "hb-wasm-shape.cc"

View File

@ -493,6 +493,13 @@ struct hb_buffer_t
HB_NODISCARD HB_INTERNAL bool enlarge (unsigned int size);
HB_NODISCARD bool resize (unsigned length)
{
assert (!have_output);
if (unlikely (!ensure (length))) return false;
len = length;
return true;
}
HB_NODISCARD bool ensure (unsigned int size)
{ return likely (!size || size < allocated) ? true : enlarge (size); }

View File

@ -389,6 +389,10 @@ struct hb_no_trace_t {
#define HB_DEBUG_UNISCRIBE (HB_DEBUG+0)
#endif
#ifndef HB_DEBUG_WASM
#define HB_DEBUG_WASM (HB_DEBUG+0)
#endif
/*
* With tracing.
*/

View File

@ -106,6 +106,13 @@ HB_BEGIN_DECLS
*/
#mesondefine HB_HAS_UNISCRIBE
/**
* HB_HAS_WASM:
*
* Defined if Harfbuzz has been built with WebAssembly support.
*/
#mesondefine HB_HAS_WASM
HB_END_DECLS

View File

@ -248,6 +248,21 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
gr_fref_set_feature_value (fref, features[i].value, feats);
}
hb_direction_t direction = buffer->props.direction;
hb_direction_t horiz_dir = hb_script_get_horizontal_direction (buffer->props.script);
/* TODO vertical:
* The only BTT vertical script is Ogham, but it's not clear to me whether OpenType
* Ogham fonts are supposed to be implemented BTT or not. Need to research that
* first. */
if ((HB_DIRECTION_IS_HORIZONTAL (direction) &&
direction != horiz_dir && horiz_dir != HB_DIRECTION_INVALID) ||
(HB_DIRECTION_IS_VERTICAL (direction) &&
direction != HB_DIRECTION_TTB))
{
hb_buffer_reverse_clusters (buffer);
direction = HB_DIRECTION_REVERSE (direction);
}
gr_segment *seg = nullptr;
const gr_slot *is;
unsigned int ci = 0, ic = 0;
@ -261,21 +276,11 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
for (unsigned int i = 0; i < buffer->len; ++i)
chars[i] = buffer->info[i].codepoint;
/* TODO ensure_native_direction. */
hb_tag_t script_tag[HB_OT_MAX_TAGS_PER_SCRIPT];
unsigned int count = HB_OT_MAX_TAGS_PER_SCRIPT;
hb_ot_tags_from_script_and_language (hb_buffer_get_script (buffer),
HB_LANGUAGE_INVALID,
&count,
script_tag,
nullptr, nullptr);
seg = gr_make_seg (nullptr, grface,
count ? script_tag[count - 1] : HB_OT_TAG_DEFAULT_SCRIPT,
HB_TAG_NONE, // https://github.com/harfbuzz/harfbuzz/issues/3439#issuecomment-1442650148
feats,
gr_utf32, chars, buffer->len,
2 | (hb_buffer_get_direction (buffer) == HB_DIRECTION_RTL ? 1 : 0));
2 | (direction == HB_DIRECTION_RTL ? 1 : 0));
if (unlikely (!seg)) {
if (feats) gr_featureval_destroy (feats);
@ -327,7 +332,7 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
float yscale = (float) font->y_scale / upem;
yscale *= yscale / xscale;
unsigned int curradv = 0;
if (HB_DIRECTION_IS_BACKWARD(buffer->props.direction))
if (HB_DIRECTION_IS_BACKWARD (direction))
{
curradv = gr_slot_origin_X(gr_seg_first_slot(seg)) * xscale;
clusters[0].advance = gr_seg_advance_X(seg) * xscale - curradv;
@ -356,7 +361,7 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
c->num_chars = before - c->base_char;
c->base_glyph = ic;
c->num_glyphs = 0;
if (HB_DIRECTION_IS_BACKWARD(buffer->props.direction))
if (HB_DIRECTION_IS_BACKWARD (direction))
{
c->advance = curradv - gr_slot_origin_X(is) * xscale;
curradv -= c->advance;
@ -375,7 +380,7 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
clusters[ci].num_chars = after + 1 - clusters[ci].base_char;
}
if (HB_DIRECTION_IS_BACKWARD(buffer->props.direction))
if (HB_DIRECTION_IS_BACKWARD (direction))
clusters[ci].advance += curradv;
else
clusters[ci].advance += gr_seg_advance_X(seg) * xscale - curradv;
@ -397,7 +402,7 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan HB_UNUSED,
unsigned int currclus = UINT_MAX;
const hb_glyph_info_t *info = buffer->info;
hb_glyph_position_t *pPos = hb_buffer_get_glyph_positions (buffer, nullptr);
if (!HB_DIRECTION_IS_BACKWARD(buffer->props.direction))
if (!HB_DIRECTION_IS_BACKWARD (direction))
{
curradvx = 0;
for (is = gr_seg_first_slot (seg); is; pPos++, ++info, is = gr_slot_next_in_segment (is))

View File

@ -33,6 +33,11 @@
/* v--- Add new shapers in the right place here. */
#ifdef HAVE_WASM
/* Only picks up fonts that have a "Wasm" table. */
HB_SHAPER_IMPLEMENT (wasm)
#endif
#ifdef HAVE_GRAPHITE2
/* Only picks up fonts that have a "Silf" table. */
HB_SHAPER_IMPLEMENT (graphite2)

50
src/hb-wasm-api-blob.hh Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_BLOB_HH
#define HB_WASM_API_BLOB_HH
#include "hb-wasm-api.hh"
namespace hb {
namespace wasm {
HB_WASM_API (void, blob_free) (HB_WASM_EXEC_ENV
ptr_d(blob_t, blob))
{
HB_PTR_PARAM (blob_t, blob);
if (unlikely (!blob))
return;
module_free (blob->data);
blob->data = nullref;
blob->length = 0;
}
}}
#endif /* HB_WASM_API_BLOB_HH */

217
src/hb-wasm-api-buffer.hh Normal file
View File

@ -0,0 +1,217 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_BUFFER_HH
#define HB_WASM_API_BUFFER_HH
#include "hb-wasm-api.hh"
#include "hb-buffer.hh"
namespace hb {
namespace wasm {
static_assert (sizeof (glyph_info_t) == sizeof (hb_glyph_info_t), "");
static_assert (sizeof (glyph_position_t) == sizeof (hb_glyph_position_t), "");
HB_WASM_API (bool_t, buffer_contents_realloc) (HB_WASM_EXEC_ENV
ptr_d(buffer_contents_t, contents),
uint32_t size)
{
HB_PTR_PARAM (buffer_contents_t, contents);
if (unlikely (!contents))
return false;
if (size <= contents->length)
return true;
unsigned bytes;
if (hb_unsigned_mul_overflows (size, sizeof (glyph_info_t), &bytes))
return false;
glyph_info_t *info = HB_ARRAY_APP2NATIVE (glyph_info_t, contents->info, contents->length);
glyph_position_t *pos = HB_ARRAY_APP2NATIVE (glyph_position_t, contents->pos, contents->length);
if (unlikely (!info || !pos))
return false;
glyph_info_t *new_info = nullptr;
uint32_t new_inforef = module_malloc (bytes, (void **) &new_info);
glyph_position_t *new_pos = nullptr;
uint32_t new_posref = module_malloc (bytes, (void **) &new_pos);
unsigned old_bytes = contents->length * sizeof (glyph_info_t);
if (likely (new_inforef))
{
memcpy (new_info, info, old_bytes);
module_free (contents->info);
contents->info = new_inforef;
}
if (likely (new_posref))
{
memcpy (new_pos, pos, old_bytes);
module_free (contents->pos);
contents->pos = new_posref;
}
if (likely (new_info && new_pos))
{
contents->length = size;
return true;
}
return false;
}
HB_WASM_API (void, buffer_contents_free) (HB_WASM_EXEC_ENV
ptr_d(buffer_contents_t, contents))
{
HB_PTR_PARAM (buffer_contents_t, contents);
if (unlikely (!contents))
return;
module_free (contents->info);
module_free (contents->pos);
contents->info = nullref;
contents->pos = nullref;
contents->length = 0;
}
HB_WASM_API (bool_t, buffer_copy_contents) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer),
ptr_d(buffer_contents_t, contents))
{
HB_REF2OBJ (buffer);
HB_PTR_PARAM (buffer_contents_t, contents);
if (unlikely (!contents))
return false;
if (buffer->have_output)
buffer->sync ();
if (!buffer->have_positions)
buffer->clear_positions ();
unsigned length = buffer->len;
if (length <= contents->length)
{
glyph_info_t *info = HB_ARRAY_APP2NATIVE (glyph_info_t, contents->info, length);
glyph_position_t *pos = HB_ARRAY_APP2NATIVE (glyph_position_t, contents->pos, length);
if (unlikely (!info || !pos))
{
contents->length = 0;
return false;
}
unsigned bytes = length * sizeof (hb_glyph_info_t);
memcpy (info, buffer->info, bytes);
memcpy (pos, buffer->pos, bytes);
return true;
}
module_free (contents->info);
module_free (contents->pos);
contents->length = length;
unsigned bytes = length * sizeof (hb_glyph_info_t);
contents->info = wasm_runtime_module_dup_data (module_inst, (const char *) buffer->info, bytes);
contents->pos = wasm_runtime_module_dup_data (module_inst, (const char *) buffer->pos, bytes);
if (length && (!contents->info || !contents->pos))
{
contents->length = 0;
return false;
}
return true;
}
HB_WASM_API (bool_t, buffer_set_contents) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer),
ptr_d(const buffer_contents_t, contents))
{
HB_REF2OBJ (buffer);
HB_PTR_PARAM (buffer_contents_t, contents);
if (unlikely (!contents))
return false;
unsigned length = contents->length;
unsigned bytes;
if (unlikely (hb_unsigned_mul_overflows (length, sizeof (buffer->info[0]), &bytes)))
return false;
if (unlikely (!buffer->resize (length)))
return false;
glyph_info_t *info = (glyph_info_t *) (validate_app_addr (contents->info, bytes) ? addr_app_to_native (contents->info) : nullptr);
glyph_position_t *pos = (glyph_position_t *) (validate_app_addr (contents->pos, bytes) ? addr_app_to_native (contents->pos) : nullptr);
if (!buffer->have_positions)
buffer->clear_positions (); /* This is wasteful. */
memcpy (buffer->info, info, bytes);
memcpy (buffer->pos, pos, bytes);
buffer->len = length;
return true;
}
HB_WASM_API (direction_t, buffer_get_direction) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer))
{
HB_REF2OBJ (buffer);
return (direction_t) hb_buffer_get_direction (buffer);
}
HB_WASM_API (script_t, buffer_get_script) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer))
{
HB_REF2OBJ (buffer);
return hb_script_to_iso15924_tag (hb_buffer_get_script (buffer));
}
HB_WASM_API (void, buffer_reverse) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer))
{
HB_REF2OBJ (buffer);
hb_buffer_reverse (buffer);
}
HB_WASM_API (void, buffer_reverse_clusters) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer))
{
HB_REF2OBJ (buffer);
hb_buffer_reverse_clusters (buffer);
}
}}
#endif /* HB_WASM_API_BUFFER_HH */

44
src/hb-wasm-api-common.hh Normal file
View File

@ -0,0 +1,44 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_COMMON_HH
#define HB_WASM_API_COMMON_HH
#include "hb-wasm-api.hh"
namespace hb {
namespace wasm {
HB_WASM_API (direction_t, script_get_horizontal_direction) (HB_WASM_EXEC_ENV
script_t script)
{
return (direction_t)
hb_script_get_horizontal_direction (hb_script_from_iso15924_tag (script));
}
}}
#endif /* HB_WASM_API_COMMON_HH */

109
src/hb-wasm-api-face.hh Normal file
View File

@ -0,0 +1,109 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_FACE_HH
#define HB_WASM_API_FACE_HH
#include "hb-wasm-api.hh"
namespace hb {
namespace wasm {
HB_WASM_API (ptr_t(face_t), face_create) (HB_WASM_EXEC_ENV
ptr_d(blob_t, blob),
unsigned int index)
{
HB_PTR_PARAM (blob_t, blob);
hb_blob_t *hb_blob = hb_blob_create(
HB_ARRAY_APP2NATIVE (char, blob->data, blob->length),
blob->length,
HB_MEMORY_MODE_DUPLICATE,
NULL,
NULL);
hb_face_t *face = hb_face_create(hb_blob, index);
HB_OBJ2REF (face);
return faceref;
}
HB_WASM_API (bool_t, face_copy_table) (HB_WASM_EXEC_ENV
ptr_d(face_t, face),
tag_t table_tag,
ptr_d(blob_t, blob))
{
HB_REF2OBJ (face);
HB_PTR_PARAM (blob_t, blob);
if (unlikely (!blob))
return false;
hb_blob_t *hb_blob = hb_face_reference_table (face, table_tag);
unsigned length;
const char *hb_data = hb_blob_get_data (hb_blob, &length);
if (length <= blob->length)
{
char *data = HB_ARRAY_APP2NATIVE (char, blob->data, length);
if (unlikely (!data))
{
blob->length = 0;
return false;
}
memcpy (data, hb_data, length);
return true;
}
module_free (blob->data);
blob->length = length;
blob->data = wasm_runtime_module_dup_data (module_inst, hb_data, length);
hb_blob_destroy (hb_blob);
if (blob->length && !blob->data)
{
blob->length = 0;
return false;
}
return true;
}
HB_WASM_API (unsigned, face_get_upem) (HB_WASM_EXEC_ENV
ptr_d(face_t, face))
{
HB_REF2OBJ (face);
return hb_face_get_upem (face);
}
}}
#endif /* HB_WASM_API_FACE_HH */

263
src/hb-wasm-api-font.hh Normal file
View File

@ -0,0 +1,263 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_FONT_HH
#define HB_WASM_API_FONT_HH
#include "hb-wasm-api.hh"
#include "hb-outline.hh"
namespace hb {
namespace wasm {
HB_WASM_API (ptr_t(font_t), font_create) (HB_WASM_EXEC_ENV
ptr_d(face_t, face))
{
HB_REF2OBJ (face);
hb_font_t *font = hb_font_create (face);
HB_OBJ2REF (font);
return fontref;
}
HB_WASM_API (ptr_t(face_t), font_get_face) (HB_WASM_EXEC_ENV
ptr_d(font_t, font))
{
HB_REF2OBJ (font);
hb_face_t *face = hb_font_get_face (font);
HB_OBJ2REF (face);
return faceref;
}
HB_WASM_API (void, font_get_scale) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(int32_t, x_scale),
ptr_d(int32_t, y_scale))
{
HB_REF2OBJ (font);
HB_PTR_PARAM(int32_t, x_scale);
HB_PTR_PARAM(int32_t, y_scale);
hb_font_get_scale (font, x_scale, y_scale);
}
HB_WASM_API (codepoint_t, font_get_glyph) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t unicode,
codepoint_t variation_selector)
{
HB_REF2OBJ (font);
codepoint_t glyph;
hb_font_get_glyph (font, unicode, variation_selector, &glyph);
return glyph;
}
HB_WASM_API (position_t, font_get_glyph_h_advance) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph)
{
HB_REF2OBJ (font);
return hb_font_get_glyph_h_advance (font, glyph);
}
HB_WASM_API (position_t, font_get_glyph_v_advance) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph)
{
HB_REF2OBJ (font);
return hb_font_get_glyph_v_advance (font, glyph);
}
static_assert (sizeof (glyph_extents_t) == sizeof (hb_glyph_extents_t), "");
HB_WASM_API (bool_t, font_get_glyph_extents) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
ptr_d(glyph_extents_t, extents))
{
HB_REF2OBJ (font);
HB_PTR_PARAM (glyph_extents_t, extents);
if (unlikely (!extents))
return false;
return hb_font_get_glyph_extents (font, glyph,
(hb_glyph_extents_t *) extents);
}
HB_WASM_API (void, font_glyph_to_string) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
char *s, uint32_t size)
{
HB_REF2OBJ (font);
hb_font_glyph_to_string (font, glyph, s, size);
}
static_assert (sizeof (glyph_outline_point_t) == sizeof (hb_outline_point_t), "");
static_assert (sizeof (uint32_t) == sizeof (hb_outline_t::contours[0]), "");
HB_WASM_API (bool_t, font_copy_glyph_outline) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
ptr_d(glyph_outline_t, outline))
{
HB_REF2OBJ (font);
HB_PTR_PARAM (glyph_outline_t, outline);
if (unlikely (!outline))
return false;
hb_outline_t hb_outline;
auto *funcs = hb_outline_recording_pen_get_funcs ();
hb_font_draw_glyph (font, glyph, funcs, &hb_outline);
if (unlikely (hb_outline.points.in_error () ||
hb_outline.contours.in_error ()))
{
outline->n_points = outline->n_contours = 0;
return false;
}
// TODO Check two buffers separately
if (hb_outline.points.length <= outline->n_points &&
hb_outline.contours.length <= outline->n_contours)
{
glyph_outline_point_t *points = HB_ARRAY_APP2NATIVE (glyph_outline_point_t, outline->points, hb_outline.points.length);
uint32_t *contours = HB_ARRAY_APP2NATIVE (uint32_t, outline->contours, hb_outline.contours.length);
if (unlikely (!points || !contours))
{
outline->n_points = outline->n_contours = 0;
return false;
}
memcpy (points, hb_outline.points.arrayZ, hb_outline.points.get_size ());
memcpy (contours, hb_outline.contours.arrayZ, hb_outline.contours.get_size ());
return true;
}
outline->n_points = hb_outline.points.length;
outline->points = wasm_runtime_module_dup_data (module_inst,
(const char *) hb_outline.points.arrayZ,
hb_outline.points.get_size ());
outline->n_contours = hb_outline.contours.length;
outline->contours = wasm_runtime_module_dup_data (module_inst,
(const char *) hb_outline.contours.arrayZ,
hb_outline.contours.get_size ());
if ((outline->n_points && !outline->points) ||
(!outline->n_contours && !outline->contours))
{
outline->n_points = outline->n_contours = 0;
return false;
}
return true;
}
HB_WASM_API (void, glyph_outline_free) (HB_WASM_EXEC_ENV
ptr_d(glyph_outline_t, outline))
{
HB_PTR_PARAM (glyph_outline_t, outline);
if (unlikely (!outline))
return;
module_free (outline->points);
module_free (outline->contours);
outline->n_points = 0;
outline->points = nullref;
outline->n_contours = 0;
outline->contours = nullref;
}
HB_WASM_API (bool_t, font_copy_coords) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(coords_t, coords))
{
HB_REF2OBJ (font);
HB_PTR_PARAM (coords_t, coords);
if (unlikely (!coords))
return false;
unsigned our_length;
const int* our_coords = hb_font_get_var_coords_normalized(font, &our_length);
if (our_length <= coords->length) {
int *their_coords = HB_ARRAY_APP2NATIVE (int, coords->coords, our_length);
if (unlikely(!their_coords)) {
coords->length = 0;
return false;
}
unsigned bytes = our_length * sizeof (int);
memcpy (their_coords, our_coords, bytes);
return true;
}
module_free (coords->coords);
coords->length = our_length;
unsigned bytes = our_length * sizeof (int);
coords->coords = wasm_runtime_module_dup_data (module_inst, (const char *) our_coords, bytes);
if (our_length && !coords->coords)
{
coords->length = 0;
return false;
}
return true;
}
HB_WASM_API (bool_t, font_set_coords) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(coords_t, coords))
{
HB_REF2OBJ (font);
HB_PTR_PARAM (coords_t, coords);
if (unlikely (!coords))
return false;
unsigned length = coords->length;
unsigned bytes;
if (unlikely (hb_unsigned_mul_overflows (length, sizeof (int), &bytes)))
return false;
const int *our_coords = (const int *) (validate_app_addr (coords->coords, bytes) ? addr_app_to_native (coords->coords) : nullptr);
hb_font_set_var_coords_normalized(font, our_coords, length);
return true;
}
}}
#endif /* HB_WASM_API_FONT_HH */

109
src/hb-wasm-api-list.hh Normal file
View File

@ -0,0 +1,109 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_LIST_HH
#define HB_WASM_API_LIST_HH
#include "hb-wasm-api.hh"
#ifdef HB_DEBUG_WASM
namespace hb { namespace wasm {
static void debugprint (HB_WASM_EXEC_ENV char *str)
{ DEBUG_MSG (WASM, exec_env, "%s", str); }
static void debugprint1 (HB_WASM_EXEC_ENV char *str, int32_t i1)
{ DEBUG_MSG (WASM, exec_env, "%s: %d", str, i1); }
static void debugprint2 (HB_WASM_EXEC_ENV char *str, int32_t i1, int32_t i2)
{ DEBUG_MSG (WASM, exec_env, "%s: %d, %d", str, i1, i2); }
static void debugprint3 (HB_WASM_EXEC_ENV char *str, int32_t i1, int32_t i2, int32_t i3)
{ DEBUG_MSG (WASM, exec_env, "%s: %d, %d, %d", str, i1, i2, i3); }
static void debugprint4 (HB_WASM_EXEC_ENV char *str, int32_t i1, int32_t i2, int32_t i3, int32_t i4)
{ DEBUG_MSG (WASM, exec_env, "%s: %d, %d, %d, %d", str, i1, i2, i3, i4); }
}}
#endif
#define NATIVE_SYMBOL(signature, name) {#name, (void *) hb::wasm::name, signature, NULL}
/* Note: the array must be static defined since runtime will keep it after registration.
* Also not const, because it modifies it (sorts it).
* https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/doc/export_native_api.md
*
* TODO Allocate this lazily in _hb_wasm_init(). */
static NativeSymbol _hb_wasm_native_symbols[] =
{
/* common */
NATIVE_SYMBOL ("(i)i", script_get_horizontal_direction),
/* blob */
NATIVE_SYMBOL ("(i)", blob_free),
/* buffer */
NATIVE_SYMBOL ("(i)", buffer_contents_free),
NATIVE_SYMBOL ("(ii)i", buffer_contents_realloc),
NATIVE_SYMBOL ("(ii)i", buffer_copy_contents),
NATIVE_SYMBOL ("(ii)i", buffer_set_contents),
NATIVE_SYMBOL ("(i)i", buffer_get_direction),
NATIVE_SYMBOL ("(i)i", buffer_get_script),
NATIVE_SYMBOL ("(i)", buffer_reverse),
NATIVE_SYMBOL ("(i)", buffer_reverse_clusters),
/* face */
NATIVE_SYMBOL ("(ii)i", face_create),
NATIVE_SYMBOL ("(iii)i", face_copy_table),
NATIVE_SYMBOL ("(i)i", face_get_upem),
/* font */
NATIVE_SYMBOL ("(i)i", font_create),
NATIVE_SYMBOL ("(i)i", font_get_face),
NATIVE_SYMBOL ("(iii)", font_get_scale),
NATIVE_SYMBOL ("(iii)i", font_get_glyph),
NATIVE_SYMBOL ("(ii)i", font_get_glyph_h_advance),
NATIVE_SYMBOL ("(ii)i", font_get_glyph_v_advance),
NATIVE_SYMBOL ("(iii)i", font_get_glyph_extents),
NATIVE_SYMBOL ("(ii$*)", font_glyph_to_string),
NATIVE_SYMBOL ("(iii)i", font_copy_glyph_outline),
NATIVE_SYMBOL ("(ii)i", font_copy_coords),
NATIVE_SYMBOL ("(ii)i", font_set_coords),
/* outline */
NATIVE_SYMBOL ("(i)", glyph_outline_free),
/* shape */
NATIVE_SYMBOL ("(iiii$)i", shape_with),
/* debug */
#ifdef HB_DEBUG_WASM
NATIVE_SYMBOL ("($)", debugprint),
NATIVE_SYMBOL ("($i)", debugprint1),
NATIVE_SYMBOL ("($ii)", debugprint2),
NATIVE_SYMBOL ("($iii)", debugprint3),
NATIVE_SYMBOL ("($iiii)", debugprint4),
#endif
};
#undef NATIVE_SYMBOL
#endif /* HB_WASM_API_LIST_HH */

70
src/hb-wasm-api-shape.hh Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_SHAPE_HH
#define HB_WASM_API_SHAPE_HH
#include "hb-wasm-api.hh"
namespace hb {
namespace wasm {
static_assert (sizeof (feature_t) == sizeof (hb_feature_t), "");
HB_WASM_INTERFACE (bool_t, shape_with) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(buffer_t, buffer),
ptr_d(const feature_t, features),
uint32_t num_features,
const char *shaper)
{
if (unlikely (0 == strcmp (shaper, "wasm")))
return false;
HB_REF2OBJ (font);
HB_REF2OBJ (buffer);
/* Pre-conditions that make hb_shape_full() crash should be checked here. */
if (unlikely (!buffer->ensure_unicode ()))
return false;
if (unlikely (!HB_DIRECTION_IS_VALID (buffer->props.direction)))
return false;
HB_ARRAY_PARAM (const feature_t, features, num_features);
if (unlikely (!features && num_features))
return false;
const char * shaper_list[] = {shaper, nullptr};
return hb_shape_full (font, buffer,
(hb_feature_t *) features, num_features,
shaper_list);
}
}}
#endif /* HB_WASM_API_SHAPE_HH */

46
src/hb-wasm-api.cc Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#include "hb.hh"
#ifdef HAVE_WASM
#include "hb-wasm-api.hh"
#define module_inst wasm_runtime_get_module_inst (exec_env)
#include "hb-wasm-api-blob.hh"
#include "hb-wasm-api-buffer.hh"
#include "hb-wasm-api-common.hh"
#include "hb-wasm-api-face.hh"
#include "hb-wasm-api-font.hh"
#include "hb-wasm-api-shape.hh"
#undef module_inst
hb_user_data_key_t _hb_wasm_ref_type_key = {};
#endif

319
src/hb-wasm-api.h Normal file
View File

@ -0,0 +1,319 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_H
#define HB_WASM_API_H
/*
#include "hb.h"
*/
#include <stdint.h>
#ifndef HB_WASM_BEGIN_DECLS
# ifdef __cplusplus
# define HB_WASM_BEGIN_DECLS extern "C" {
# define HB_WASM_END_DECLS }
# else /* !__cplusplus */
# define HB_WASM_BEGIN_DECLS
# define HB_WASM_END_DECLS
# endif /* !__cplusplus */
#endif
HB_WASM_BEGIN_DECLS
#ifndef HB_WASM_API
#define HB_WASM_API(ret_t, name) ret_t name
#endif
#ifndef HB_WASM_API_COMPOUND /* compound return type */
#define HB_WASM_API_COMPOUND(ret_t, name) HB_WASM_API(ret_t, name)
#endif
#ifndef HB_WASM_INTERFACE
#define HB_WASM_INTERFACE(ret_t, name) ret_t name
#endif
#ifndef HB_WASM_EXEC_ENV
#define HB_WASM_EXEC_ENV
#endif
#ifndef HB_WASM_EXEC_ENV_COMPOUND
#define HB_WASM_EXEC_ENV_COMPOUND HB_WASM_EXEC_ENV
#endif
#ifndef bool_t
#define bool_t uint32_t
#endif
#ifndef ptr_t
#define ptr_t(type_t) type_t *
#endif
#ifndef ptr_d
#define ptr_d(type_t, name) type_t *name
#endif
typedef uint32_t codepoint_t;
typedef int32_t position_t;
typedef uint32_t mask_t;
typedef uint32_t tag_t;
#define TAG(c1,c2,c3,c4) ((tag_t)((((uint32_t)(c1)&0xFF)<<24)|(((uint32_t)(c2)&0xFF)<<16)|(((uint32_t)(c3)&0xFF)<<8)|((uint32_t)(c4)&0xFF)))
typedef enum {
DIRECTION_INVALID = 0,
DIRECTION_LTR = 4,
DIRECTION_RTL,
DIRECTION_TTB,
DIRECTION_BTT
} direction_t;
#define DIRECTION_IS_VALID(dir) ((((unsigned int) (dir)) & ~3U) == 4)
#define DIRECTION_IS_HORIZONTAL(dir) ((((unsigned int) (dir)) & ~1U) == 4)
#define DIRECTION_IS_VERTICAL(dir) ((((unsigned int) (dir)) & ~1U) == 6)
#define DIRECTION_IS_FORWARD(dir) ((((unsigned int) (dir)) & ~2U) == 4)
#define DIRECTION_IS_BACKWARD(dir) ((((unsigned int) (dir)) & ~2U) == 5)
#define DIRECTION_REVERSE(dir) ((direction_t) (((unsigned int) (dir)) ^ 1))
typedef tag_t script_t; /* ISO 15924 representation of Unicode scripts. */
/* common */
HB_WASM_API (direction_t, script_get_horizontal_direction) (HB_WASM_EXEC_ENV
script_t script);
/* blob */
typedef struct
{
uint32_t length;
ptr_t(char) data;
} blob_t;
#define BLOB_INIT {0, 0}
HB_WASM_API (void, blob_free) (HB_WASM_EXEC_ENV
ptr_d(blob_t, blob));
/* buffer */
typedef struct
{
uint32_t codepoint;
uint32_t mask;
uint32_t cluster;
uint32_t var1;
uint32_t var2;
} glyph_info_t;
typedef struct
{
position_t x_advance;
position_t y_advance;
position_t x_offset;
position_t y_offset;
uint32_t var;
} glyph_position_t;
typedef struct
{
uint32_t length;
ptr_t(glyph_info_t) info;
ptr_t(glyph_position_t) pos;
} buffer_contents_t;
#define BUFFER_CONTENTS_INIT {0, 0, 0}
HB_WASM_API (bool_t, buffer_contents_realloc) (HB_WASM_EXEC_ENV
ptr_d(buffer_contents_t, contents),
uint32_t size);
HB_WASM_API (void, buffer_contents_free) (HB_WASM_EXEC_ENV
ptr_d(buffer_contents_t, contents));
typedef struct buffer_t buffer_t;
HB_WASM_API (bool_t, buffer_copy_contents) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer),
ptr_d(buffer_contents_t, contents));
HB_WASM_API (bool_t, buffer_set_contents) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer),
ptr_d(const buffer_contents_t, contents));
HB_WASM_API (direction_t, buffer_get_direction) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer));
HB_WASM_API (script_t, buffer_get_script) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer));
HB_WASM_API (void, buffer_reverse) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer));
HB_WASM_API (void, buffer_reverse_clusters) (HB_WASM_EXEC_ENV
ptr_d(buffer_t, buffer));
/* face */
typedef struct face_t face_t;
HB_WASM_API (ptr_t(face_t), face_create) (HB_WASM_EXEC_ENV
ptr_d(blob_t, blob),
unsigned int);
HB_WASM_API (bool_t, face_copy_table) (HB_WASM_EXEC_ENV
ptr_d(face_t, face),
tag_t table_tag,
ptr_d(blob_t, blob));
HB_WASM_API (unsigned, face_get_upem) (HB_WASM_EXEC_ENV
ptr_d(face_t, face));
/* font */
typedef struct font_t font_t;
HB_WASM_API (ptr_t(font_t), font_create) (HB_WASM_EXEC_ENV
ptr_d(face_t, face));
HB_WASM_API (ptr_t(face_t), font_get_face) (HB_WASM_EXEC_ENV
ptr_d(font_t, font));
HB_WASM_API (void, font_get_scale) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(int32_t, x_scale),
ptr_d(int32_t, y_scale));
HB_WASM_API (codepoint_t, font_get_glyph) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t unicode,
codepoint_t variation_selector);
HB_WASM_API (position_t, font_get_glyph_h_advance) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph);
HB_WASM_API (position_t, font_get_glyph_v_advance) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph);
typedef struct
{
position_t x_bearing;
position_t y_bearing;
position_t width;
position_t height;
} glyph_extents_t;
HB_WASM_API (bool_t, font_get_glyph_extents) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
ptr_d(glyph_extents_t, extents));
HB_WASM_API (void, font_glyph_to_string) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
char *s, uint32_t size);
typedef struct
{
unsigned int length;
ptr_t(int) coords;
} coords_t;
HB_WASM_API (bool_t, font_copy_coords) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(coords_t, coords));
HB_WASM_API (bool_t, font_set_coords) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(coords_t, coords));
/* outline */
enum glyph_outline_point_type_t
{
MOVE_TO,
LINE_TO,
QUADRATIC_TO,
CUBIC_TO,
};
typedef struct
{
float x;
float y;
uint32_t type;
} glyph_outline_point_t;
typedef struct
{
uint32_t n_points;
ptr_t(glyph_outline_point_t) points;
uint32_t n_contours;
ptr_t(uint32_t) contours;
} glyph_outline_t;
#define GLYPH_OUTLINE_INIT {0, 0, 0, 0}
HB_WASM_API (void, glyph_outline_free) (HB_WASM_EXEC_ENV
ptr_d(glyph_outline_t, outline));
HB_WASM_API (bool_t, font_copy_glyph_outline) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
codepoint_t glyph,
ptr_d(glyph_outline_t, outline));
/* shape */
typedef struct
{
tag_t tag;
uint32_t value;
uint32_t start;
uint32_t end;
} feature_t;
#define FEATURE_GLOBAL_START 0
#define FEATURE_GLOBAL_END ((uint32_t) -1)
HB_WASM_API (bool_t, shape_with) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(buffer_t, buffer),
ptr_d(const feature_t, features),
uint32_t num_features,
const char *shaper);
/* Implement these in your shaper. */
HB_WASM_INTERFACE (ptr_t(void), shape_plan_create) (ptr_d(face_t, face));
HB_WASM_INTERFACE (bool_t, shape) (ptr_d(void, shape_plan),
ptr_d(font_t, font),
ptr_d(buffer_t, buffer),
ptr_d(const feature_t, features),
uint32_t num_features);
HB_WASM_INTERFACE (void, shape_plan_destroy) (ptr_d(void, shape_plan));
HB_WASM_END_DECLS
#endif /* HB_WASM_API_H */

117
src/hb-wasm-api.hh Normal file
View File

@ -0,0 +1,117 @@
/*
* Copyright © 2023 Behdad Esfahbod
*
* 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.
*/
#ifndef HB_WASM_API_HH
#define HB_WASM_API_HH
#include "hb.hh"
#include <wasm_export.h>
#define HB_WASM_BEGIN_DECLS namespace hb { namespace wasm {
#define HB_WASM_END_DECLS }}
#define HB_WASM_API(ret_t, name) HB_INTERNAL ret_t name
#define HB_WASM_API_COMPOUND(ret_t, name) HB_INTERNAL void name
#define HB_WASM_EXEC_ENV wasm_exec_env_t exec_env,
#define HB_WASM_EXEC_ENV_COMPOUND wasm_exec_env_t exec_env, ptr_t() retptr,
#define ptr_t(type_t) uint32_t
#define ptr_d(type_t, name) uint32_t name##ptr
#include "hb-wasm-api.h"
#undef HB_WASM_BEGIN_DECLS
#undef HB_WASM_END_DECLS
enum {
hb_wasm_ref_type_none,
hb_wasm_ref_type_face,
hb_wasm_ref_type_font,
hb_wasm_ref_type_buffer,
};
HB_INTERNAL extern hb_user_data_key_t _hb_wasm_ref_type_key;
#define nullref 0
#define HB_REF2OBJ(obj) \
hb_##obj##_t *obj = nullptr; \
HB_STMT_START { \
(void) wasm_externref_ref2obj (obj##ptr, (void **) &obj); \
/* Check object type. */ \
/* This works because all our objects have the same hb_object_t layout. */ \
if (unlikely (hb_##obj##_get_user_data (obj, &_hb_wasm_ref_type_key) != \
(void *) hb_wasm_ref_type_##obj)) \
obj = hb_##obj##_get_empty (); \
} HB_STMT_END
#define HB_OBJ2REF(obj) \
uint32_t obj##ref = nullref; \
HB_STMT_START { \
hb_##obj##_set_user_data (obj, &_hb_wasm_ref_type_key, \
(void *) hb_wasm_ref_type_##obj, \
nullptr, false); \
(void) wasm_externref_obj2ref (module_inst, obj, &obj##ref); \
} HB_STMT_END
#define HB_RETURN_STRUCT(type, name) \
type *_name_ptr = nullptr; \
{ \
if (likely (wasm_runtime_validate_app_addr (module_inst, \
retptr, sizeof (type)))) \
{ \
_name_ptr = (type *) wasm_runtime_addr_app_to_native (module_inst, retptr); \
if (unlikely (!_name_ptr)) \
return; \
} \
} \
type &name = *_name_ptr
#define HB_PTR_PARAM(type, name) \
type *name = nullptr; \
HB_STMT_START { \
if (likely (wasm_runtime_validate_app_addr (module_inst, \
name##ptr, sizeof (type)))) \
name = (type *) wasm_runtime_addr_app_to_native (module_inst, name##ptr); \
} HB_STMT_END
#define HB_ARRAY_PARAM(type, name, length) \
type *name = nullptr; \
HB_STMT_START { \
if (likely (!hb_unsigned_mul_overflows (length, sizeof (type)) && \
wasm_runtime_validate_app_addr (module_inst, \
name##ptr, length * sizeof (type)))) \
name = (type *) wasm_runtime_addr_app_to_native (module_inst, name##ptr); \
} HB_STMT_END
#define HB_ARRAY_APP2NATIVE(type, name, length) \
((type *) (!hb_unsigned_mul_overflows (length, sizeof (type)) && \
validate_app_addr (name, (length) * sizeof (type)) ? \
addr_app_to_native (name) : nullptr))
#endif /* HB_WASM_API_HH */

469
src/hb-wasm-shape.cc Normal file
View File

@ -0,0 +1,469 @@
/*
* 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
*/
#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;
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

View File

@ -324,6 +324,19 @@ hb_glib_headers = files('hb-glib.h')
hb_graphite2_sources = files('hb-graphite2.cc')
hb_graphite2_headers = files('hb-graphite2.h')
hb_wasm_sources = files(
'hb-wasm-api.cc',
'hb-wasm-api.hh',
'hb-wasm-api-blob.hh',
'hb-wasm-api-buffer.hh',
'hb-wasm-api-common.hh',
'hb-wasm-api-face.hh',
'hb-wasm-api-font.hh',
'hb-wasm-api-shape.hh',
'hb-wasm-shape.cc',
)
hb_wasm_headers = files()
# System-dependent sources and headers
hb_coretext_sources = files('hb-coretext.cc')
@ -416,7 +429,7 @@ custom_target('harfbuzz.cc',
output: 'harfbuzz.cc',
input: hb_base_sources + hb_glib_sources + hb_ft_sources +
hb_graphite2_sources + hb_uniscribe_sources + hb_gdi_sources +
hb_directwrite_sources + hb_coretext_sources,
hb_directwrite_sources + hb_coretext_sources + hb_wasm_sources,
command: [find_program('gen-harfbuzzcc.py'),
'@OUTPUT@', meson.current_source_dir(), '@INPUT@'],
)
@ -459,6 +472,13 @@ if conf.get('HAVE_GRAPHITE2', 0) == 1
harfbuzz_deps += [graphite2_dep, graphite_dep]
endif
if conf.get('HAVE_WASM', 0) == 1
hb_sources += hb_wasm_sources
hb_headers += hb_wasm_headers
harfbuzz_deps += wasm_dep
#harfbuzz_deps += llvm_dep
endif
if conf.get('HAVE_UNISCRIBE', 0) == 1
hb_sources += hb_uniscribe_sources
hb_headers += hb_uniscribe_headers
@ -496,6 +516,7 @@ features = [
'GRAPHITE',
'ICU',
'UNISCRIBE',
'WASM',
]
hb_enabled_features = configuration_data()
@ -909,7 +930,10 @@ if get_option('tests').enabled()
env.set('HBHEADERS', ' '.join(HBHEADERS))
if cpp.get_argument_syntax() != 'msvc' and not meson.is_cross_build() # ensure the local tools are usable
dist_check_script += ['check-libstdc++', 'check-static-inits', 'check-symbols']
dist_check_script += ['check-static-inits', 'check-symbols']
if get_option('wasm').disabled()
dist_check_script += ['check-libstdc++']
endif
endif
foreach name : dist_check_script

View File

@ -0,0 +1,28 @@
FONTS = CharisSIL-R.wasm.ttf Scheherazade-R.wasm.ttf AwamiNastaliq-Regular.wasm.ttf
ADD_TABLE = ../../addTable.py
all: $(FONTS)
%.wasm: %.cc ../../hb-wasm-api.h
emcc \
-I ../.. \
-I ~/graphite/include/ \
-fvisibility=hidden \
-Wl,--allow-undefined \
-Wl,--no-entry \
-Wl,--strip-all \
-sERROR_ON_UNDEFINED_SYMBOLS=0 \
-Wl,--export=malloc -Wl,--export=free \
~/graphite/src/libgraphite2.a \
~/wasm/wasi-sdk-19.0/share/wasi-sysroot/lib/wasm32-wasi/libc.a \
$< \
-o $@
%.wasm.ttf: %.ttf shape.wasm $(ADD_TABLE)
python $(ADD_TABLE) $< $@ shape.wasm
clean:
$(RM) shape.wasm $(FONTS)
.PRECIOUS: shape.wasm

249
src/wasm/graphite/shape.cc Normal file
View File

@ -0,0 +1,249 @@
#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name
#include <hb-wasm-api.h>
#include <graphite2/Segment.h>
#include <stdlib.h>
#include <string.h>
void debugprint1 (char *s, int32_t);
void debugprint2 (char *s, int32_t, int32_t);
static const void *copy_table (const void *data, unsigned int tag, size_t *len)
{
face_t *face = (face_t *) data;
blob_t blob = BLOB_INIT;
if (!face_copy_table (face, tag, &blob))
abort ();
*len = blob.length;
return blob.data;
}
static void free_table (const void *data, const void *table_data)
{
blob_t blob;
blob.length = 0; // Doesn't matter
blob.data = (char *) table_data;
blob_free (&blob);
}
void *
shape_plan_create (face_t *face)
{
const gr_face_ops ops = {sizeof (gr_face_ops), &copy_table, &free_table};
gr_face *grface = gr_make_face_with_ops (face, &ops, gr_face_preloadAll);
return grface;
}
void
shape_plan_destroy (void *data)
{
gr_face_destroy ((gr_face *) data);
}
bool_t
shape (void *shape_plan,
font_t *font,
buffer_t *buffer,
const feature_t *features,
uint32_t num_features)
{
face_t *face = font_get_face (font);
gr_face *grface = (gr_face *) shape_plan;
direction_t direction = buffer_get_direction (buffer);
direction_t horiz_dir = script_get_horizontal_direction (buffer_get_script (buffer));
/* TODO vertical:
* The only BTT vertical script is Ogham, but it's not clear to me whether OpenType
* Ogham fonts are supposed to be implemented BTT or not. Need to research that
* first. */
if ((DIRECTION_IS_HORIZONTAL (direction) &&
direction != horiz_dir && horiz_dir != DIRECTION_INVALID) ||
(DIRECTION_IS_VERTICAL (direction) &&
direction != DIRECTION_TTB))
{
buffer_reverse_clusters (buffer);
direction = DIRECTION_REVERSE (direction);
}
buffer_contents_t contents = BUFFER_CONTENTS_INIT;
if (!buffer_copy_contents (buffer, &contents))
return false;
gr_segment *seg = nullptr;
const gr_slot *is;
unsigned int ci = 0, ic = 0;
unsigned int curradvx = 0, curradvy = 0;
unsigned length = contents.length;
uint32_t *chars = (uint32_t *) malloc (length * sizeof (uint32_t));
if (!chars)
return false;
for (unsigned int i = 0; i < contents.length; ++i)
chars[i] = contents.info[i].codepoint;
seg = gr_make_seg (nullptr, grface,
0, // https://github.com/harfbuzz/harfbuzz/issues/3439#issuecomment-1442650148
nullptr,
gr_utf32, chars, contents.length,
2 | (direction == DIRECTION_RTL ? 1 : 0));
free (chars);
if (!seg)
return false;
unsigned int glyph_count = gr_seg_n_slots (seg);
struct cluster_t {
unsigned int base_char;
unsigned int num_chars;
unsigned int base_glyph;
unsigned int num_glyphs;
unsigned int cluster;
int advance;
};
length = glyph_count;
if (!buffer_contents_realloc (&contents, length))
return false;
cluster_t *clusters = (cluster_t *) malloc (length * sizeof (cluster_t));
uint32_t *gids = (uint32_t *) malloc (length * sizeof (uint32_t));
if (!clusters || !gids)
{
free (clusters);
free (gids);
return false;
}
memset (clusters, 0, sizeof (clusters[0]) * length);
codepoint_t *pg = gids;
clusters[0].cluster = contents.info[0].cluster;
unsigned int upem = face_get_upem (face);
int32_t font_x_scale, font_y_scale;
font_get_scale (font, &font_x_scale, &font_y_scale);
float xscale = (float) font_x_scale / upem;
float yscale = (float) font_y_scale / upem;
yscale *= yscale / xscale;
unsigned int curradv = 0;
if (DIRECTION_IS_BACKWARD (direction))
{
curradv = gr_slot_origin_X(gr_seg_first_slot(seg)) * xscale;
clusters[0].advance = gr_seg_advance_X(seg) * xscale - curradv;
}
else
clusters[0].advance = 0;
for (is = gr_seg_first_slot (seg), ic = 0; is; is = gr_slot_next_in_segment (is), ic++)
{
unsigned int before = gr_slot_before (is);
unsigned int after = gr_slot_after (is);
*pg = gr_slot_gid (is);
pg++;
while (clusters[ci].base_char > before && ci)
{
clusters[ci-1].num_chars += clusters[ci].num_chars;
clusters[ci-1].num_glyphs += clusters[ci].num_glyphs;
clusters[ci-1].advance += clusters[ci].advance;
ci--;
}
if (gr_slot_can_insert_before (is) && clusters[ci].num_chars && before >= clusters[ci].base_char + clusters[ci].num_chars)
{
cluster_t *c = clusters + ci + 1;
c->base_char = clusters[ci].base_char + clusters[ci].num_chars;
c->cluster = contents.info[c->base_char].cluster;
c->num_chars = before - c->base_char;
c->base_glyph = ic;
c->num_glyphs = 0;
if (DIRECTION_IS_BACKWARD (direction))
{
c->advance = curradv - gr_slot_origin_X(is) * xscale;
curradv -= c->advance;
}
else
{
c->advance = 0;
clusters[ci].advance += gr_slot_origin_X(is) * xscale - curradv;
curradv += clusters[ci].advance;
}
ci++;
}
clusters[ci].num_glyphs++;
if (clusters[ci].base_char + clusters[ci].num_chars < after + 1)
clusters[ci].num_chars = after + 1 - clusters[ci].base_char;
}
if (DIRECTION_IS_BACKWARD (direction))
clusters[ci].advance += curradv;
else
clusters[ci].advance += gr_seg_advance_X(seg) * xscale - curradv;
ci++;
for (unsigned int i = 0; i < ci; ++i)
{
for (unsigned int j = 0; j < clusters[i].num_glyphs; ++j)
{
glyph_info_t *info = &contents.info[clusters[i].base_glyph + j];
info->codepoint = gids[clusters[i].base_glyph + j];
info->cluster = clusters[i].cluster;
info->var1 = (unsigned) clusters[i].advance; // all glyphs in the cluster get the same advance
}
}
contents.length = glyph_count;
/* Positioning. */
unsigned int currclus = 0xFFFFFFFF;
const glyph_info_t *info = contents.info;
glyph_position_t *pPos = contents.pos;
if (!DIRECTION_IS_BACKWARD (direction))
{
curradvx = 0;
for (is = gr_seg_first_slot (seg); is; pPos++, ++info, is = gr_slot_next_in_segment (is))
{
pPos->x_offset = gr_slot_origin_X (is) * xscale - curradvx;
pPos->y_offset = gr_slot_origin_Y (is) * yscale - curradvy;
if (info->cluster != currclus) {
pPos->x_advance = (int) info->var1;
curradvx += pPos->x_advance;
currclus = info->cluster;
} else
pPos->x_advance = 0.;
pPos->y_advance = gr_slot_advance_Y (is, grface, nullptr) * yscale;
curradvy += pPos->y_advance;
}
buffer_set_contents (buffer, &contents);
}
else
{
curradvx = gr_seg_advance_X(seg) * xscale;
for (is = gr_seg_first_slot (seg); is; pPos++, info++, is = gr_slot_next_in_segment (is))
{
if (info->cluster != currclus)
{
pPos->x_advance = (int) info->var1;
curradvx -= pPos->x_advance;
currclus = info->cluster;
} else
pPos->x_advance = 0.;
pPos->y_advance = gr_slot_advance_Y (is, grface, nullptr) * yscale;
curradvy -= pPos->y_advance;
pPos->x_offset = gr_slot_origin_X (is) * xscale - (int) info->var1 - curradvx + pPos->x_advance;
pPos->y_offset = gr_slot_origin_Y (is) * yscale - curradvy;
}
buffer_set_contents (buffer, &contents);
buffer_reverse_clusters (buffer);
}
gr_seg_destroy (seg);
free (clusters);
free (gids);
bool ret = glyph_count;
return ret;
}

View File

@ -0,0 +1,9 @@
[package]
name = "harfbuzz-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kurbo = { version = "0.9.0", optional = true }

View File

@ -0,0 +1,464 @@
#![warn(missing_docs)]
#![allow(dead_code)]
//! Interface to Harfbuzz's WASM exports
//!
//! This crate is designed to make it easier to write a
//! WASM shaper for your font using Rust. It binds the
//! functions exported by Harfbuzz into the WASM runtime,
//! and wraps them in an ergonomic interface using Rust
//! structures. For example, here is a basic shaping engine:
//!
//!
//! ```rust
//! #[wasm_bindgen]
//! pub fn shape(font_ref: u32, buf_ref: u32) -> i32 {
//! let font = Font::from_ref(font_ref);
//! let mut buffer = GlyphBuffer::from_ref(buf_ref);
//! for mut item in buffer.glyphs.iter_mut() {
//! // Map character to glyph
//! item.codepoint = font.get_glyph(codepoint, 0);
//! // Set advance width
//! item.h_advance = font.get_glyph_h_advance(item.codepoint);
//! }
//! 1
//! }
//! ```
use std::ffi::{c_int, CStr, CString};
#[cfg(feature = "kurbo")]
use kurbo::BezPath;
// We don't use #[wasm_bindgen] here because that makes
// assumptions about Javascript calling conventions. We
// really do just want to import some C symbols and run
// them in unsafe-land!
extern "C" {
fn face_get_upem(face: u32) -> u32;
fn font_get_face(font: u32) -> u32;
fn font_get_glyph(font: u32, unicode: u32, uvs: u32) -> u32;
fn font_get_scale(font: u32, x_scale: *mut i32, y_scale: *mut i32);
fn font_get_glyph_extents(font: u32, glyph: u32, extents: *mut CGlyphExtents) -> bool;
fn font_glyph_to_string(font: u32, glyph: u32, str: *const u8, len: u32);
fn font_get_glyph_h_advance(font: u32, glyph: u32) -> i32;
fn font_get_glyph_v_advance(font: u32, glyph: u32) -> i32;
fn font_copy_glyph_outline(font: u32, glyph: u32, outline: *mut CGlyphOutline) -> bool;
fn face_copy_table(font: u32, tag: u32, blob: *mut Blob) -> bool;
fn buffer_copy_contents(buffer: u32, cbuffer: *mut CBufferContents) -> bool;
fn buffer_set_contents(buffer: u32, cbuffer: &CBufferContents) -> bool;
fn debugprint(s: *const u8);
fn shape_with(
font: u32,
buffer: u32,
features: u32,
num_features: u32,
shaper: *const u8,
) -> i32;
}
/// An opaque reference to a font at a given size and
/// variation. It is equivalent to the `hb_font_t` pointer
/// in Harfbuzz.
#[derive(Debug)]
pub struct Font(u32);
impl Font {
/// Initialize a `Font` struct from the reference provided
/// by Harfbuzz to the `shape` function.
pub fn from_ref(ptr: u32) -> Self {
Self(ptr)
}
/// Call the given Harfbuzz shaper on a buffer reference.
///
/// For example, `font.shape_with(buffer_ref, "ot")` will
/// run standard OpenType shaping, allowing you to modify
/// the buffer contents after glyph mapping, substitution
/// and positioning has taken place.
pub fn shape_with(&self, buffer_ref: u32, shaper: &str) {
let c_shaper = CString::new(shaper).unwrap();
unsafe {
shape_with(self.0, buffer_ref, 0, 0, c_shaper.as_ptr() as *const u8);
}
}
/// Return the font face object that this font belongs to.
pub fn get_face(&self) -> Face {
Face(unsafe { font_get_face(self.0) })
}
/// Map a Unicode codepoint to a glyph ID.
///
/// The `uvs` parameter specifies a Unicode Variation
/// Selector codepoint which is used in conjunction with
/// [format 14 cmap tables](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences)
/// to provide alternate glyph mappings for characters with
/// Unicode Variation Sequences. Generally you will pass
/// `0`.
pub fn get_glyph(&self, unicode: u32, uvs: u32) -> u32 {
unsafe { font_get_glyph(self.0, unicode, uvs) }
}
/// Get the extents for a given glyph ID, in its design position.
pub fn get_glyph_extents(&self, glyph: u32) -> CGlyphExtents {
let mut extents = CGlyphExtents::default();
unsafe {
font_get_glyph_extents(self.0, glyph, &mut extents);
}
extents
}
/// Get the default advance width for a given glyph ID.
pub fn get_glyph_h_advance(&self, glyph: u32) -> i32 {
unsafe { font_get_glyph_h_advance(self.0, glyph) }
}
/// Get the default vertical advance for a given glyph ID.
fn get_glyph_v_advance(&self, glyph: u32) -> i32 {
unsafe { font_get_glyph_v_advance(self.0, glyph) }
}
/// Get the name of a glyph.
///
/// If no names are provided by the font, names of the form
/// `gidXXX` are constructed.
pub fn get_glyph_name(&self, glyph: u32) -> String {
let mut s = [1u8; 32];
unsafe {
font_glyph_to_string(self.0, glyph, s.as_mut_ptr(), 32);
}
unsafe { CStr::from_ptr(s.as_ptr() as *const _) }
.to_str()
.unwrap()
.to_string()
}
/// Get the X and Y scale factor applied to this font.
///
/// This should be divided by the units per em value to
/// provide a scale factor mapping from design units to
/// user units. (See [`Face::get_upem`].)
pub fn get_scale(&self) -> (i32, i32) {
let mut x_scale: i32 = 0;
let mut y_scale: i32 = 0;
unsafe {
font_get_scale(
self.0,
&mut x_scale as *mut c_int,
&mut y_scale as *mut c_int,
)
};
(x_scale, y_scale)
}
#[cfg(feature = "kurbo")]
/// Get the outline of a glyph as a vector of bezier paths
pub fn get_outline(&self, glyph: u32) -> Vec<BezPath> {
let mut outline = CGlyphOutline {
n_points: 0,
points: std::ptr::null_mut(),
n_contours: 0,
contours: std::ptr::null_mut(),
};
let end_pts_of_contours: &[usize] = unsafe {
font_copy_glyph_outline(self.0, glyph, &mut outline);
std::slice::from_raw_parts(outline.contours, outline.n_contours as usize)
};
let points: &[CGlyphOutlinePoint] =
unsafe { std::slice::from_raw_parts(outline.points, outline.n_points as usize) };
let mut results: Vec<BezPath> = vec![];
let mut start_pt: usize = 0;
for end_pt in end_pts_of_contours {
let this_contour = &points[start_pt..*end_pt];
start_pt = *end_pt;
let mut path = BezPath::new();
let mut ix = 0;
while ix < this_contour.len() {
let point = &this_contour[ix];
match point.pointtype {
PointType::MoveTo => path.move_to((point.x as f64, point.y as f64)),
PointType::LineTo => path.line_to((point.x as f64, point.y as f64)),
PointType::QuadraticTo => {
ix += 1;
let end_pt = &this_contour[ix];
path.quad_to(
(point.x as f64, point.y as f64),
(end_pt.x as f64, end_pt.y as f64),
);
}
PointType::CubicTo => {
ix += 1;
let mid_pt = &this_contour[ix];
ix += 1;
let end_pt = &this_contour[ix];
path.curve_to(
(point.x as f64, point.y as f64),
(mid_pt.x as f64, mid_pt.y as f64),
(end_pt.x as f64, end_pt.y as f64),
);
}
}
ix += 1;
}
path.close_path();
results.push(path);
}
results
}
}
/// An opaque reference to a font face, equivalent to the `hb_face_t` pointer
/// in Harfbuzz.
///
/// This is generally returned from [`Font::get_face`].
#[derive(Debug)]
pub struct Face(u32);
impl Face {
/// Get a blob containing the contents of the given binary font table.
pub fn reference_table(&self, tag: &str) -> Blob {
let mut tag_u: u32 = 0;
let mut chars = tag.chars();
tag_u |= (chars.next().unwrap() as u32) << 24;
tag_u |= (chars.next().unwrap() as u32) << 16;
tag_u |= (chars.next().unwrap() as u32) << 8;
tag_u |= chars.next().unwrap() as u32;
let mut blob = Blob {
data: std::ptr::null_mut(),
length: 0,
};
unsafe {
face_copy_table(self.0, tag_u, &mut blob);
}
blob
}
/// Get the face's design units per em.
pub fn get_upem(&self) -> u32 {
unsafe { face_get_upem(self.0) }
}
}
/// Trait implemented by custom structs representing buffer items
pub trait BufferItem {
/// Construct an item in your preferred representation out of the info and position data provided by Harfbuzz.
fn from_c(info: CGlyphInfo, position: CGlyphPosition) -> Self;
/// Return info and position data to Harfbuzz.
fn to_c(self) -> (CGlyphInfo, CGlyphPosition);
}
/// Generic representation of a Harfbuzz buffer item.
///
/// By making this generic, we allow you to implement your own
/// representations of buffer items; for example, in your shaper,
/// you may want certain fields to keep track of the glyph's name,
/// extents, or shape, so you would want a custom struct to represent
/// buffer items. If you don't care about any of them, use the
/// supplied `GlyphBuffer` struct.
#[derive(Debug)]
pub struct Buffer<T: BufferItem> {
_ptr: u32,
/// Glyphs in the buffer
pub glyphs: Vec<T>,
}
impl<T: BufferItem> Buffer<T> {
/// Construct a buffer from the pointer Harfbuzz provides to the WASM.
///
/// The `Buffer` struct implements Drop, meaning that when the shaping
/// function is finished, the buffer contents are sent back to Harfbuzz.
pub fn from_ref(ptr: u32) -> Self {
let mut c_contents = CBufferContents {
info: std::ptr::null_mut(),
position: std::ptr::null_mut(),
length: 0,
};
unsafe {
buffer_copy_contents(ptr, &mut c_contents) || panic!("Couldn't copy buffer contents")
};
let positions: Vec<CGlyphPosition> = unsafe {
std::slice::from_raw_parts(c_contents.position, c_contents.length as usize).to_vec()
};
let infos: Vec<CGlyphInfo> = unsafe {
std::slice::from_raw_parts(c_contents.info, c_contents.length as usize).to_vec()
};
Buffer {
glyphs: infos
.into_iter()
.zip(positions.into_iter())
.map(|(i, p)| T::from_c(i, p))
.collect(),
_ptr: ptr,
}
}
}
impl<T: BufferItem> Drop for Buffer<T> {
fn drop(&mut self) {
let mut positions: Vec<CGlyphPosition>;
let mut infos: Vec<CGlyphInfo>;
let glyphs = std::mem::take(&mut self.glyphs);
(infos, positions) = glyphs.into_iter().map(|g| g.to_c()).unzip();
let c_contents = CBufferContents {
length: positions.len() as u32,
info: infos[..].as_mut_ptr(),
position: positions[..].as_mut_ptr(),
};
unsafe {
if !buffer_set_contents(self._ptr, &c_contents) {
panic!("Couldn't set buffer contents");
}
}
}
}
/// Some data provided by Harfbuzz.
#[derive(Debug)]
#[repr(C)]
pub struct Blob {
/// Length of the blob in bytes
pub length: u32,
/// A raw pointer to the contents
pub data: *mut u8,
}
/// Glyph information in a buffer item provided by Harfbuzz
///
/// You'll only need to interact with this if you're writing
/// your own buffer item structure.
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGlyphInfo {
pub codepoint: u32,
pub mask: u32,
pub cluster: u32,
pub var1: u32,
pub var2: u32,
}
/// Glyph positioning information in a buffer item provided by Harfbuzz
///
/// You'll only need to interact with this if you're writing
/// your own buffer item structure.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CGlyphPosition {
pub x_advance: i32,
pub y_advance: i32,
pub x_offset: i32,
pub y_offset: i32,
pub var: u32,
}
/// Glyph extents
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct CGlyphExtents {
/// The scaled left side bearing of the glyph
pub x_bearing: i32,
/// The scaled coordinate of the top of the glyph
pub y_bearing: i32,
/// The width of the glyph
pub width: i32,
/// The height of the glyph
pub height: i32,
}
#[derive(Debug)]
#[repr(C)]
struct CBufferContents {
length: u32,
info: *mut CGlyphInfo,
position: *mut CGlyphPosition,
}
/// Ergonomic representation of a Harfbuzz buffer item
///
/// Harfbuzz buffers are normally split into two arrays,
/// one representing glyph information and the other
/// representing glyph positioning. In Rust, this would
/// require lots of zipping and unzipping, so we zip them
/// together into a single structure for you.
#[derive(Debug, Clone, Copy)]
pub struct Glyph {
/// The Unicode codepoint or glyph ID of the item
pub codepoint: u32,
/// The index of the cluster in the input text where this came from
pub cluster: u32,
/// The horizontal advance of the glyph
pub x_advance: i32,
/// The vertical advance of the glyph
pub y_advance: i32,
/// The horizontal offset of the glyph
pub x_offset: i32,
/// The vertical offset of the glyph
pub y_offset: i32,
/// You can use this for whatever you like
pub flags: u32,
}
impl BufferItem for Glyph {
fn from_c(info: CGlyphInfo, pos: CGlyphPosition) -> Self {
Self {
codepoint: info.codepoint,
cluster: info.cluster,
x_advance: pos.x_advance,
y_advance: pos.y_advance,
x_offset: pos.x_offset,
y_offset: pos.y_offset,
flags: 0,
}
}
fn to_c(self) -> (CGlyphInfo, CGlyphPosition) {
let info = CGlyphInfo {
codepoint: self.codepoint,
cluster: self.cluster,
mask: 0,
var1: 0,
var2: 0,
};
let pos = CGlyphPosition {
x_advance: self.x_advance,
y_advance: self.y_advance,
x_offset: self.x_offset,
y_offset: self.y_offset,
var: 0,
};
(info, pos)
}
}
#[repr(C)]
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Debug)]
enum PointType {
MoveTo,
LineTo,
QuadraticTo,
CubicTo,
}
#[repr(C)]
#[derive(Clone, Debug)]
struct CGlyphOutlinePoint {
x: f32,
y: f32,
pointtype: PointType,
}
#[repr(C)]
struct CGlyphOutline {
n_points: usize,
points: *mut CGlyphOutlinePoint,
n_contours: usize,
contours: *mut usize,
}
/// Our default buffer item struct. See also [`Glyph`].
pub type GlyphBuffer = Buffer<Glyph>;
/// Write a string to the Harfbuzz debug log.
pub fn debug(s: &str) {
let c_s = CString::new(s).unwrap();
unsafe {
debugprint(c_s.as_ptr() as *const u8);
};
}

View File

@ -0,0 +1,25 @@
ADD_TABLE = ../../../addTable.py
all: test-fallback.wasm.ttf test-ot.wasm.ttf
%.wasm: %.cc ../../../hb-wasm-api.h
clang \
--target=wasm32-unknown-wasi \
-Wl,--no-entry \
-fvisibility=hidden \
-Wl,--allow-undefined \
-nostdlib \
-I ../../.. \
$< \
-o $@
test-fallback.wasm.ttf: test.ttf shape-fallback.wasm $(ADD_TABLE)
python $(ADD_TABLE) $< $@ shape-fallback.wasm
test-ot.wasm.ttf: test.ttf shape-ot.wasm $(ADD_TABLE)
python $(ADD_TABLE) $< $@ shape-ot.wasm
clean:
$(RM) test-fallback.wasm.ttf test-ot.wasm.ttf shape-fallback.wasm shape-ot.wasm
.PRECIOUS: *.wasm

View File

@ -0,0 +1,60 @@
#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name
#include <hb-wasm-api.h>
extern "C" {
void debugprint (const char *s);
void debugprint1 (const char *s, int32_t);
void debugprint2 (const char *s, int32_t, int32_t);
}
bool_t
shape (void *shape_plan,
font_t *font,
buffer_t *buffer,
const feature_t *features,
uint32_t num_features)
{
face_t *face = font_get_face (font);
blob_t blob = BLOB_INIT;
face_copy_table (face, TAG ('c','m','a','p'), &blob);
debugprint1 ("cmap length", blob.length);
blob_free (&blob);
buffer_contents_t contents = BUFFER_CONTENTS_INIT;
if (!buffer_copy_contents (buffer, &contents))
return false;
debugprint1 ("buffer length", contents.length);
glyph_outline_t outline = GLYPH_OUTLINE_INIT;
for (unsigned i = 0; i < contents.length; i++)
{
char name[64];
debugprint1 ("glyph at", i);
font_glyph_to_string (font, contents.info[i].codepoint, name, sizeof (name));
debugprint (name);
contents.info[i].codepoint = font_get_glyph (font, contents.info[i].codepoint, 0);
contents.pos[i].x_advance = font_get_glyph_h_advance (font, contents.info[i].codepoint);
font_copy_glyph_outline (font, contents.info[i].codepoint, &outline);
debugprint1 ("num outline points", outline.n_points);
debugprint1 ("num outline contours", outline.n_contours);
}
glyph_outline_free (&outline);
bool_t ret = buffer_set_contents (buffer, &contents);
buffer_contents_free (&contents);
return ret;
}

View File

@ -0,0 +1,18 @@
#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name
#include <hb-wasm-api.h>
extern "C" {
void debugprint1 (const char *s, int32_t);
void debugprint2 (const char *s, int32_t, int32_t);
}
bool_t
shape (void *shape_plan,
font_t *font,
buffer_t *buffer,
const feature_t *features,
uint32_t num_features)
{
return shape_with (font, buffer, features, num_features, "ot");
}

BIN
src/wasm/sample/c/test.ttf Normal file

Binary file not shown.

View File

@ -0,0 +1,13 @@
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
#externref = "0.1.0"
wasm-bindgen = "0.2.0"
tiny-rng = "0.2.0"
harfbuzz-wasm = { path="../../../rust/harfbuzz-wasm"}

View File

@ -0,0 +1,24 @@
use harfbuzz_wasm::{Font, GlyphBuffer};
use tiny_rng::{Rand, Rng};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn shape(
_shape_plan: u32,
font_ref: u32,
buf_ref: u32,
_features: u32,
_num_features: u32,
) -> i32 {
let mut rng = Rng::from_seed(123456);
let font = Font::from_ref(font_ref);
font.shape_with(buf_ref, "ot");
let mut buffer = GlyphBuffer::from_ref(buf_ref);
for mut item in buffer.glyphs.iter_mut() {
// Randomize it!
item.x_offset = ((rng.rand_u32() as i32) >> 24) - 120;
item.y_offset = ((rng.rand_u32() as i32) >> 24) - 120;
}
// Buffer is written back to HB on drop
1
}