Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suport for WOFF File Format #43

Closed
quasado opened this issue May 27, 2014 · 20 comments · Fixed by #161
Closed

Suport for WOFF File Format #43

quasado opened this issue May 27, 2014 · 20 comments · Fixed by #161

Comments

@quasado
Copy link

quasado commented May 27, 2014

Are there any plans to support Web-Fonts (WOFF) i.e. to read from Google-Fonts and the such? As far as I understood, on could actually use zlib.js to decompress the woff format?

@fdb
Copy link
Contributor

fdb commented Jun 10, 2014

It would be cool to support WOFF. I haven't looked at the spec yet, but because of the compression it is more bandwith-friendly with minimal changes.

@quasado
Copy link
Author

quasado commented Jun 10, 2014

Yes, I guess uncompressing would be easy via the zlib.js. However, I couldn't really understand what the underlying format of WOFF (after decompression) would be, as far as I understood it could be any of TTF, OpenType etc?

Btw., here's an implementation of reading WOFF Headers, might help:

https://github.com/bramstein/opentype

@raphaelyancey
Copy link

I'd be happy to see WOFF support too!

@timo22345
Copy link

I made an example of woff to otf conversion: http://jsbin.com/rijuvad.

It uses https://github.com/arty-name/woff2otf (which is based on python version), which uses https://github.com/nodeca/pako/blob/master/dist/pako_inflate.min.js.

The text above is rendered by opentype.js draw() function and the text below is normal fillText:

screen shot 2015-09-25 at 02 26 08

There is also newer woff2, but the support is currently so low that basically using woff (and SVG/EOT/TTF/OTF) are needed at least few years to the future:
screen shot 2015-09-25 at 02 12 30

@timo22345
Copy link

opentype.min.js is 79.9 KB.

woff2otf.min.js is 1.13KB.

pako_inflate.min.js. is 22.19KB.

Pako says it is "Almost as fast in modern JS engines as C implementation".

Could this be the method to allow loading WOFF into opentype.js?

If reverse is needed (is it?), then there is need for also Pako deflate which is 26.3 KB.

Other possibility is to make use of browser's native deflate/inflate, if "the lock" is some day removed. So far I have not found a native way, but it surely is there, because browser can decompress on the fly files that are deflated on server and browser can read/write compressed PNG files.

@fdb
Copy link
Contributor

fdb commented Sep 26, 2015

Cool! I'll look at Pako next week.

@timo22345
Copy link

I was a bit worried about adding 22.19KB Pako Inflate to opentype.js, and fortunately I found another library, Imaya zlib.js, which is only 6.82KB and seems to be significantly faster than Pako (at least in this very limited test with one woff font). Devongovett send then a message (see below) and provided even smaller library Tiny-Inflate, which is only 3.74KB. And this provided to be even faster than Imaya.

TINY-INFLATE:
Test: http://jsbin.com/qiwase/edit?html,js,output
Source: https://raw.githubusercontent.com/devongovett/tiny-inflate/master/index.js
Source size minified: 3.74KB
Inflate execution time: 6 ms

IMAYA:
Test: http://jsbin.com/zororo/edit?html,js,output
Source: https://raw.githubusercontent.com/imaya/zlib.js/master/bin/inflate.min.js
Source size minified: 6.82KB
Inflate execution time: 15 ms

PAKO:
Test: http://jsbin.com/wunipa/edit?html,js,output
Source: https://github.com/nodeca/pako/blob/master/dist/pako_inflate.min.js
Source size minified: 22.19KB
Inflate execution time: 37 ms

(All times are measured in Chrome OSX Macbook Pro Mid 2010. Fastest of ten samples.)

If I understand the things right, woff can have multiple compressed/non-compressed tables, which means that every table needs to be decompressed individually. The following confirms that:

The main body of the file consists of the same collection of font data tables as the input sfnt font, stored in the same order, except that each table MAY be compressed, and the sfnt table directory is replaced by the WOFF table directory.

( http://www.w3.org/TR/2012/REC-WOFF-20121213/#OverallStructure )

And arty-name:s woff2otf does just this table "separation" and calls inflate every time when compressed table is found. I assume that usually webfont glyph amount and other tabledata are tried to keep as minimal as possible, so such inflate library that can decompress fast many small data blocks is the winner.

Of course it is wise to test with many different woff:s before making a decision which inflate library to choose.

@devongovett
Copy link

If you're worried about file size, https://github.com/devongovett/tiny-inflate is only 1.3KB min+gzipped. Probably not the fastest library, but it's definitely one of the smallest.

FWIW, not sure how you're measuring performance, but pako has been the fastest library in my tests, and according to their own benchmarks.

@timo22345
Copy link

I got also Tiny-Inflate working. It gave Data error, but it was some 2-byte header which had to be stripped:

http://jsbin.com/qiwase/edit?html,js,output

This is how I measured the performance:

TINY-INFLATE:

window.time_cumul = 0;
function zlib_decompress(buffer, decompressedSize) {
  var time = performance.now();
        buffer = new Uint8Array(buffer);
        buffer = buffer.subarray(2,buffer.length); // strip header
        var plain = new Uint8Array(decompressedSize);
        window.inflate(buffer, plain);
  time = performance.now()-time;
  window.time_cumul += time;
  window.time_div.innerHTML = 'Decompress time (ms): ' + window.time_cumul;
  return plain;
}

IMAYA:

window.time_cumul = 0;
function zlib_decompress(buffer) {
  var time = performance.now();
        buffer = new Uint8Array(buffer);
        var inflate = new Zlib.Inflate(buffer);
        var plain = inflate.decompress();
  time = performance.now()-time;
  window.time_cumul += time;
  window.time_div.innerHTML = 'Decompress time (ms): ' + window.time_cumul;
  return plain;
}

PAKO:

window.time_cumul = 0;
function zlib_decompress(buffer) {
  var time = performance.now();
        var inflate = new pako.Inflate();
        inflate.push(new Uint8Array(buffer), true);
  time = performance.now()-time;
  window.time_cumul += time;
  window.time_div.innerHTML = 'Decompress time (ms): ' + window.time_cumul;
  return inflate.result;
}

If you find any flaws in performance measuring, please report.

EDIT: Maybe those new pako.Inflate() and new Zlib.Inflate(buffer) could be moved outside of the function zlib_decompress() so that they are executed at the page load, but not sure about this because this is first time I use them.

@timo22345
Copy link

I made a SO question about the native DEFLATE/INFLATE support: http://stackoverflow.com/questions/32838795/native-deflate-inflate-in-browsers.

Hopefully this is coming to browsers some day. Until it we have to make use of libraries.

@timo22345
Copy link

I got also Tiny-Inflate working by stripping a 2-byte header from compressed buffers before inflation, and in addition to that it is tiny, it is also fast: only 6 ms in my test.

Pako advertises itself to be the fastest, and devongovett says that tiny-inflate is slower than Pako, but this test shows opposite. Maybe this is due to that the buffer lengths are rather small:

Compressed  Uncompressed
1264        3076
42          48
84          96
342         530
314         444
9639        16496
47          54
30          36
435         580
272         292
438         926
329         499
243         412
(in bytes)

I updated the above messages of mine to include also Tiny-Inflate.

@puzrin
Copy link

puzrin commented Sep 30, 2015

@timo22345 try to do couple of dummy passes before measurement. JS JIT needs to collect stat to warm up and reoptimize underlying code.

@timo22345
Copy link

@puzrin Thanks for the tip! I just tried that, but can't get it to go below 33ms in OSX Chrome.

@timo22345
Copy link

@puzrin Yes, you were right. It was my measurement. I was pressing always the JSBIN run button.

When I added an own Execute button, the code runs in minimum of 1.5 ms. The first run is always about 35ms, but then the next is 2 - 20 ms and finally when warmed up the final higher speed is reached. Funny!

This is the code with Execute-button:
http://jsbin.com/cevotu/1/edit?html,js,output

@timo22345
Copy link

This "warming up" means that those other libs also run faster after warmup.

The performer in unwarmed test (Tiny-Inflate) runs also in 1.2 - 1.5 ms when warmed up (using Execute-button):
http://jsbin.com/daliba/1/edit?html,js,output

I have to test these libs more with larger fonts and report here the results in various font sizes.

@puzrin
Copy link

puzrin commented Sep 30, 2015

You can do estimate without any additional measurements :) . Inflate speed is usually 30-100 mb/sec, depending on implementation. Font size is << 10mb. Unpacking will take 0.3 sec in worst case (that will never happen in real world).

@fpirsch
Copy link
Collaborator

fpirsch commented Oct 1, 2015

@timo22345 Using your examples, tiny-inflate runs always faster here (Firefox: ≈6ms before warm-up, ≈1ms after). With its tiny size, it could be a winner.

@fdb
Copy link
Contributor

fdb commented Oct 3, 2015

That looks awesome! I'll look at tiny-inflate on Monday.

@fdb
Copy link
Contributor

fdb commented Oct 5, 2015

Okay, I tried with both Pako and tiny-inflate. For tiny-inflate, I had to strip the two-byte header as @timo22345 suggested (Thanks Timo!). Both seem to be working fine, but since tiny-inflate is smaller, I prefert it over Pako.

The code is in the woff-support branch, if anyone wants to check it out.

I'll test some more with different fonts, then release later this week.

@fdb fdb changed the title Suport for WOFF File Format? Suport for WOFF File Format Oct 5, 2015
@puzrin
Copy link

puzrin commented Oct 5, 2015

@fdb, FYI there is raw deflate stream, and wrapped deflate data. Wrapped data has 2-bytes header and 4-bytes Adler checksum tail. I guess, tiny deflate supports raw streams only, but that's enougth for your needs.

@fdb fdb closed this as completed in #161 Oct 6, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants