Closed photopea closed 5 years ago
I updated it, works perfectly, thanks! :)
Using new works on HarfBuzz it turned from 440kb to 421kb and after the very recent Behdad's works it is turned into 371kb, and after enabling --llvm-lto 1
and the removal of unnecessary strings it has turned to 278kb!
But apparently I can't make it work and the previous version even doesn't work here, please make sure if it working there before updating it
@ebraminio The new size is incredible! However, for me, it returns an empty array of glyphs :(
Now a working version with 2.5.0 release which I can confirm works here also! https://harfbuzz.github.io/harfbuzzjs/ (feel free to pick the wasm from that page even or from below)
its is only 98kb of zipped wasm, exceeding your goal :)
I just put it online, works great, thanks! :)
With another round of improvements by Behdad we've reached to 246kb from 280kb and with the removal of hb_serialize, which is necessary for further works, it goes down to 236kb!
The needed change from your side is to use this instead current serializer, I tested it here https://harfbuzz.github.io/harfbuzzjs/ and seems to work fine here,
var length = module._hb_buffer_get_length(buffer);
var result = [];
var infosPtr32 = module._hb_buffer_get_glyph_infos(buffer, 0) / 4;
var positionsPtr32 = module._hb_buffer_get_glyph_positions(buffer, 0) / 4;
var infos = module.HEAPU32.slice(infosPtr32, infosPtr32 + 5 * length);
var positions = module.HEAP32.slice(positionsPtr32, positionsPtr32 + 5 * length);
for (var i = 0; i < length; ++i) {
result.push({
g: infos[i * 5 + 0],
cl: infos[i * 5 + 2],
ax: positions[i * 5 + 0],
ay: positions[i * 5 + 1],
dx: positions[i * 5 + 2],
dy: positions[i * 5 + 3]
});
}
This is essential as the next round of works I am working on is about removing emscripten and the glue code (that 10kb js code) which works with a trimmed down libc which now I have a working demo of it here https://harfbuzz.github.io/harfbuzzjs/ng/hb.html
Hi! Currently, when you open Photopea.com , 1.4 MB is loaded (the whole program). HarfBuzzjs.wasm, which is extra 111 kB (as it is GZIPped), is loaded only if the text tool is used (so we don't load it every time as in the past).
I am alerady quite happy with the progress you have made, and if you plan to keep going, I will wait for the next version :)
I wish all developers cared about the size of their programs at least half as much as you do :)
@photopea, great :) I would say this version worth to be integrated now as the next will have radical changes and we may don't release that soon or ever (as that needs we compile our owned malloc/calloc/realloc/free, which may gets some little time to correctly figured out).
An advantage to the next version is you can compile harfbuzz .wasm by yourself just by downloading llvm installer and it works even in Windows also, current llvm releases http://releases.llvm.org/download.html#8.0.0 which provide an installer for Windows, support compiling and linking wasm32 files. You may like to port some of the other codes you've written for the rest of your app to reduce their size with it, it is super easy https://dassur.ma/things/c-to-webassembly/ and doesn't need may complicated setup emscripten and Google Closure have but for now our emscripten builds are only considered stable (even the fact we have the ng builds now working here https://harfbuzz.github.io/harfbuzzjs/ng/hb.html)
So, all the new changes need is to apply this https://github.com/harfbuzz/harfbuzzjs/commit/9fc9e7aa8d83b8602639b590d81e8f8fc77ddc91#diff-291994c3e8f610097e257cfe2a68e019L33 but in your code and use the new module I've uploaded, but please check its validity before publishing it in the production. The next build will mostly need just removing underscores from the calls but needs this change also and that's why I like to encourage you to apply it now. Thanks
Hey @photopea I've just uploaded the emscripten free version of a real webassembly distribution of the project with only ~200kb size (78kb gzipped, includes a minimal libc and malloc, and without that ~10kb .js wrapper) and here is the demo, https://harfbuzz.github.io/harfbuzzjs/ (make sure you are not seeing the cached version) feel free to copy https://github.com/harfbuzz/harfbuzzjs/blob/master/examples/nohbjs.html but pick the .wasm binary from the demo page! Please note that there are differences between previous emscripten based release and this, our wasm builds have so simple sbrk
that can't grow their memory based on need so var exports = result.instance.exports; exports.memory.grow(400); // each page is 64kb in size
is put to create an initial amount of needed RAM. At the end I should note that this is not to undermine all the great works happened at emscripten project, their malloc
is still used in our libc and https://github.com/intel/zephyr/blob/master/lib/libc/minimal/source/string/string.c of Intel Zephyr libc (Apache licensed) is also used, we will review these till the release but I guess you will be fine to use our pure .wasm now!
Is there a side by side comparison of the emscripten and non-emscripten versions (or build instructions for them both)? I'm curious to understand any size difference in the .wasm. How big is that difference?
It is something like 30kb in wasm binary and 10kb in js glue code (both uncompressed), here is how to test,
$ git clone https://github.com/harfbuzz/harfbuzzjs && cd harfbuzzjs
$ ./build.sh && ls -ltrha hb.wasm # our current pure wasm module
-rwxr-xr-x 1 ebrahim 201K Jul 5 13:06 hb.wasm
$ git checkout 9fc9e7aa8d83b8602639b590d81e8f8fc77ddc91 # last version built with emscripten
$ ./build.sh && ls -ltrha harfbuzzjs.*
-rw-r--r-- 1 ebrahim 11K Jul 5 13:10 harfbuzzjs.js
-rw-r--r-- 1 ebrahim 229K Jul 5 13:10 harfbuzzjs.wasm
Downsides:
Upsides:
And we still use emscripten malloc implementation and care about emscripten heritage :)
Update: On another machine (a bot actually) with updated emscripten and both compiling amalgam, now is:
$ ./build.sh && ls -ltrha harfbuzzjs.*
-rw-r--r-- 1 9.0K Jul 5 08:06 harfbuzzjs.js
-rw-r--r-- 1 226K Jul 5 08:06 harfbuzzjs.wasm
https://transfer.sh/nOUfD/harfbuzz.wasm https://transfer.sh/qcN4B/harfbuzz.js
vs
-rwxr-xr-x 1 200K Jul 5 07:51 hb.wasm
Thanks @ebraminio!
Ok, I spent some time building the various options here - mostly I wanted to see if there were any bugs or forgotten flags or optimizations anywhere.
One issue is the emscripten version: using latest emscripten with the LLVM wasm backend, and using this modified build.sh (I took the new one you had and made it work with emscripten), I get
$ ls -alh a.*
-rw-rw-r-- 1 alon alon 6.6K Jul 5 14:02 a.out.js
-rw-rw-r-- 1 alon alon 183K Jul 5 14:02 a.out.wasm
At 183K that's better than both the emscripten and non-emscripten numbers from before.
But that brings me to the second issue: you can run Binaryen's wasm-opt tool on the non-emscripten wasm (emscripten runs it automatically), and it shrinks it to 178K,
$ wasm-opt hb.wasm -O -o hb_opt.wasm
$ ls -alh hb.wasm hb_opt.wasm
-rwxrwxr-x 1 alon alon 200K Jul 5 14:12 hb.wasm
-rw-rw-r-- 1 alon alon 178K Jul 5 14:13 hb_opt.wasm
And that 178K is the best number of all of them!
Old emscripten was using old LLVM (6). LLVM 8 and 9 (what emcc uses) can do better! Aside from that, running Binaryen typically shrinks wasm backend output by 10-15% percent (on HarfBuzz it's around 11%). With those two issues out of the way, the comparison is more apples to apples, and the difference is the 6K JS and a wasm difference of 5K, which I believe is because of the libc customization here.
Definitely on a project like HarfBuzz, that needs very little runtime support, it's nice to customize your own libc if you have time for that, and such custom runtimes can be smaller than emscripten's general-purpose runtime! The one thing though is you need to make sure to do all the things emcc
would do for you, like running wasm-opt
, otherwise the size could be worse, not better.
No dynamic memory grow in sbrk https://github.com/harfbuzz/harfbuzzjs/blob/master/libc/main.c#L11 I may need your help on this, I mean I don't know how to detect the memory grow is needed.
I think you need to track the current memory size and how close the sbrk limit gets to there. The wasm backend has intrinsics to help there (__builtin_wasm_memory_size, __builtin_wasm_memory_grow
).
Correct symbols names in wasm, something I like very much!
I'm not sure what you mean by this? If you build with emcc --profiling-funcs
for example it will keep symbol names in the wasm, emcc just doesn't emit them by default. (And when using the wasm backend the symbol names are also correct in that they don't have any extra _
prefix.)
Wow, great infromation
One issue is the emscripten version: using latest emscripten with the LLVM wasm backend, and using this modified build.sh (I took the new one you had and made it work with emscripten), I get
Great, now I think it will be nice provide both binaries in a release, considering how emscripten's is more tested.
But that brings me to the second issue: you can run Binaryen's wasm-opt tool on the non-emscripten wasm (emscripten runs it automatically), and it shrinks it to 178K,
Great! I wished I could pass 200kb limit, now I have it :)
Definitely on a project like HarfBuzz, that needs very little runtime support, it's nice to customize your own libc if you have time for that, and such custom runtimes can be smaller than emscripten's general-purpose runtime!
Our own libc is not tested like emscripten's. I will look if I can use rest of the emscripten libc from the source.
The one thing though is you need to make sure to do all the things emcc would do for you, like running wasm-opt, otherwise the size could be worse, not better.
I tried the one shipped with my distro emscripten and it just shrinked 10k, will try latest one with emsdk once I download it.
I think you need to track the current memory size and how close the sbrk limit gets to there. The wasm backend has intrinsics to help there (__builtin_wasm_memory_size, __builtin_wasm_memory_grow).
Great help, thanks :)
I'm not sure what you mean by this? If you build with emcc --profiling-funcs for example it will keep symbol names in the wasm, emcc just doesn't emit them by default. (And when using the wasm backend the symbol names are also correct in that they don't have any extra _ prefix.)
Will use the flag in our emscripten build revival if doesn't impact much the size, I like to have this aournd
I wish even I could write prototype codes in JS dynamic world then write them in native code if necessary!
Thanks :)
Transferred the issue to harfbuzz/harfbuzzjs to have access to all the information here easier.
@photopea here is our latest work using wasm-opt
optimization, hb.wasm.zip its demo is here https://harfbuzz.github.io/harfbuzzjs/ and the code you can easily adopt from which is similar to yours is here: https://github.com/harfbuzz/harfbuzzjs/blob/master/examples/nohbjs.html the difference is mostly removal of initial underscores from function names, removal of hb_direction_from_string
(which you weren't using IIRC) and removal of hb_buffer_serialize
Here is of course emscripten result also that may you can adopt easier but I haven't tested it and I don't recommend and is bigger in size: harfbuzzjs.zip
@kripken: Ok, this is the result with: emcc --profiling-funcs
(my distro's emscripten which apparently matches your numbers)
-rw-r--r-- 1 ebrahim 309K Jul 6 16:25 a.out.wasm
-rw-r--r-- 1 ebrahim 6.7K Jul 6 16:25 a.out.js
Which is better than: (but note the size impact --profiling-funcs had)
-rw-r--r-- 1 ebrahim 183K Jul 6 16:29 a.out.wasm
-rw-r--r-- 1 ebrahim 6.7K Jul 6 16:29 a.out.js
But what I like and meant for correct symbol names was this:
-rwxr-xr-x 1 ebrahim vdr 178K Jul 6 16:05 hb.wasm
@ebraminio Thanks, I think I see now, --profiling-funcs
keeps full function name info, which is why it's so big (all internal non-exported function names are kept around too). Seems you want just the export names to not be minified? Is the reason you want the unminified export names that you want to call them directly instead of through emscripten's JS code?
Emscripten automatically minifies exports (and imports) in -O3
and above. We don't have an option to specifically disable that atm, as we assume that if we emit both js and wasm that we can do optimizations on that pair together (like minifying those imports and exports, and also metadce, etc.). However, we have an option to emit just wasm, in which case you must provide all the js runtime yourself. That's not recommended for most projects since the JS is non-trivial, but maybe it's worth trying here. To try it just do -o name.wasm
. With that I get this:
$ ls -alh name.wasm
-rw-rw-r-- 1 alon alon 183K Jul 6 06:33 name.wasm
$ wasm-dis name.wasm | grep export
(export "hb_blob_create" (func $418))
(export "hb_blob_destroy" (func $28))
(export "free" (func $10))
(export "hb_blob_get_length" (func $985))
(export "malloc" (func $288))
(export "hb_buffer_create" (func $520))
(export "hb_buffer_destroy" (func $1210))
(export "hb_buffer_set_direction" (func $1205))
(export "hb_buffer_get_length" (func $1193))
(export "hb_buffer_get_glyph_infos" (func $1187))
(export "hb_buffer_get_glyph_positions" (func $504))
(export "hb_buffer_guess_segment_properties" (func $1179))
(export "hb_buffer_add_utf8" (func $1170))
(export "hb_face_create" (func $1027))
(export "hb_face_destroy" (func $468))
(export "hb_font_create" (func $948))
(export "hb_font_destroy" (func $413))
(export "hb_font_set_scale" (func $877))
(export "hb_shape" (func $1068))
(export "__errno_location" (func $1067))
(export "dynCall_vi" (func $1060))
@photopea
Accessing system fonts from a browser will probably never happen. Mainly because people are too scared of fingerprinting (a list of fonts in your OS lets the website know that it is you, no matter if you use Incognito mode, VPN etc.).
Chrome has experimental support Local Font Access API: https://bugs.chromium.org/p/chromium/issues/detail?id=535764#c67
@ebraminio @kripken
https://github.com/harfbuzz/harfbuzzjs/issues/10#issuecomment-507573424 our wasm builds have so simple sbrk that can't grow their memory based on need
Is there a way to dynamically increase the memory? For example, when doing font subsets on the server side of node.js, you may encounter very large font files, and often prompt that there is insufficient memory.
Do I just need to add this when building?
__builtin_wasm_memory_grow(0, 400);
@yisibl ideally dynamic memory increase should happen in sbrk https://github.com/harfbuzz/harfbuzzjs/blob/c2b670388cd11e7c27f21c19f417cc5fee0d6b06/libc/malloc.cc#L8 using the line you mentioned, guess I couldn't find a way to coordinate that with current code but is possible definitely.
@ebraminio It seems there is hope to support dynamic memory increase.
Hi guys, I am developing a free web-based photo editor www.Photopea.com , which is used by around 100 000 people a day. It lets people do image editing, including inserting text into a picture.
As there is no sufficient OpenType parser and layout engine in Javascript, I made my own called Typr.js. It is quite advanced and can handle e.g. Arabic text. I also use this JS implementation of BIDI algorithm.
As more and more people use Photopea, I have to extend Typr.js . Currently, I am adding the support for Urdu and Khmer layout. I am often staring at OpenType specification for 5 - 10 hours, without writing a single line of code, only trying to understand what they mean. I would be more than happy to drop Typr.js and use an alternative, if there was any.
Would you be able to provide a WebAssembly version of your library to the public, while documenting it and maintaining it? I am ready to pay 5k - 10k USD for it. It is also important, that the library is not too large (e.g. 150-200kB zipped), as every person has to download it when starting Photopea.