diff --git a/package.json b/package.json index 4751342b..57cb882e 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "build": "lerna exec --ignore=troika-examples -- rollup -c \\$LERNA_ROOT_PATH/rollup.config.js", "build-opentype": "lerna exec --scope=troika-three-text -- npm run build-opentype", "build-typr": "lerna exec --scope=troika-three-text -- npm run build-typr", + "build-woff2otf": "lerna exec --scope=troika-three-text -- npm run build-woff2otf", "build-yoga": "npm run bootstrap && lerna exec --scope=troika-flex-layout -- npm run build-yoga", "test": "jest", "build-examples": "lerna exec --scope=troika-examples -- npm run build", diff --git a/packages/troika-examples/text/TextExample.jsx b/packages/troika-examples/text/TextExample.jsx index fd4149e9..f1131071 100644 --- a/packages/troika-examples/text/TextExample.jsx +++ b/packages/troika-examples/text/TextExample.jsx @@ -331,7 +331,7 @@ class TextExample extends React.Component {
This demonstrates Troika's high quality text rendering, which uses Signed Distance Field ("SDF") texture atlases for crisp glyph vector edges at any scale. It can be used via Text3DFacade or outside the Troika framework as a standalone Three.js TextMesh.
-Behind the scenes it uses Typr.js to parse fonts, giving it support for font features such as kerning and ligatures. It generates SDF textures for each glyph on the fly as needed, assembles a single geometry for all the glyphs, seamlessly upgrades any Material's shaders to support the SDFs with high quality antialiasing, and renders the whole thing in a single draw call. Font parsing and SDF generation is done in a web worker so frames won't be dropped during processing.
+Behind the scenes it uses Typr to parse fonts, giving it support for font features such as kerning and ligatures. It generates SDF textures for each glyph on the fly as needed, assembles a single geometry for all the glyphs, seamlessly upgrades any Material's shaders to support the SDFs with high quality antialiasing, and renders the whole thing in a single draw call. Font parsing and SDF generation is done in a web worker so frames won't be dropped during processing.
Math.abs(y-v)?u=b+s.shift():v=y+s.shift(),e.U.P.curveTo(o,c,p,U,g,_,F),e.U.P.curveTo(o,S,m,b,y,u,v));else if("o14"==x){if(s.length>0&&!h&&(f=s.shift()+a.nominalWidthX,h=!0),4==s.length){var O=s.shift(),k=s.shift(),D=s.shift(),B=s.shift(),A=e.CFF.glyphBySE(a,D),L=e.CFF.glyphBySE(a,B);e.U._drawCFF(a.CharStrings[A],t,a,n,o),t.x=O,t.y=k,e.U._drawCFF(a.CharStrings[L],t,a,n,o)}d&&(e.U.P.closePath(o),d=!1)}else if("o19"==x||"o20"==x){s.length%2!=0&&!h&&(f=s.shift()+n.nominalWidthX),i+=s.length>>1,s.length=0,h=!0,l+=i+7>>3}else if("o21"==x)s.length>2&&!h&&(f=s.shift()+n.nominalWidthX,h=!0),v+=s.pop(),u+=s.pop(),d&&e.U.P.closePath(o),e.U.P.moveTo(o,u,v),d=!0;else if("o22"==x)s.length>1&&!h&&(f=s.shift()+n.nominalWidthX,h=!0),u+=s.pop(),d&&e.U.P.closePath(o),e.U.P.moveTo(o,u,v),d=!0;else if("o25"==x){for(;s.length>6;)u+=s.shift(),v+=s.shift(),e.U.P.lineTo(o,u,v);c=u+s.shift(),p=v+s.shift(),U=c+s.shift(),g=p+s.shift(),u=U+s.shift(),v=g+s.shift(),e.U.P.curveTo(o,c,p,U,g,u,v)}else if("o26"==x)for(s.length%2&&(u+=s.shift());s.length>0;)c=u,p=v+s.shift(),u=U=c+s.shift(),v=(g=p+s.shift())+s.shift(),e.U.P.curveTo(o,c,p,U,g,u,v);else if("o27"==x)for(s.length%2&&(v+=s.shift());s.length>0;)p=v,U=(c=u+s.shift())+s.shift(),g=p+s.shift(),u=U+s.shift(),v=g,e.U.P.curveTo(o,c,p,U,g,u,v);else if("o10"==x||"o29"==x){var R="o10"==x?n:a;if(0==s.length)console.warn("error: empty stack");else{var V=s.pop(),M=R.Subrs[V+R.Bias];t.x=u,t.y=v,t.nStems=i,t.haveWidth=h,t.width=f,t.open=d,e.U._drawCFF(M,t,a,n,o),u=t.x,v=t.y,i=t.nStems,h=t.haveWidth,f=t.width,d=t.open}}else if("o30"==x||"o31"==x){var W=s.length,N=(T=0,"o31"==x);for(T+=W-(P=-3&W);T
symbol translation table */
-}
-
-function Data(source, dest) {
- this.source = source;
- this.sourceIndex = 0;
- this.tag = 0;
- this.bitcount = 0;
-
- this.dest = dest;
- this.destLen = 0;
-
- this.ltree = new Tree(); /* dynamic length/symbol tree */
- this.dtree = new Tree(); /* dynamic distance tree */
-}
-
-/* --------------------------------------------------- *
- * -- uninitialized global data (static structures) -- *
- * --------------------------------------------------- */
-
-var sltree = new Tree();
-var sdtree = new Tree();
-
-/* extra bits and base tables for length codes */
-var length_bits = new Uint8Array(30);
-var length_base = new Uint16Array(30);
-
-/* extra bits and base tables for distance codes */
-var dist_bits = new Uint8Array(30);
-var dist_base = new Uint16Array(30);
-
-/* special ordering of code length codes */
-var clcidx = new Uint8Array([
- 16, 17, 18, 0, 8, 7, 9, 6,
- 10, 5, 11, 4, 12, 3, 13, 2,
- 14, 1, 15
-]);
-
-/* used by tinf_decode_trees, avoids allocations every call */
-var code_tree = new Tree();
-var lengths = new Uint8Array(288 + 32);
-
-/* ----------------------- *
- * -- utility functions -- *
- * ----------------------- */
-
-/* build extra bits and base tables */
-function tinf_build_bits_base(bits, base, delta, first) {
- var i, sum;
-
- /* build bits table */
- for (i = 0; i < delta; ++i) bits[i] = 0;
- for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0;
-
- /* build base table */
- for (sum = first, i = 0; i < 30; ++i) {
- base[i] = sum;
- sum += 1 << bits[i];
- }
-}
-
-/* build the fixed huffman trees */
-function tinf_build_fixed_trees(lt, dt) {
- var i;
-
- /* build fixed length tree */
- for (i = 0; i < 7; ++i) lt.table[i] = 0;
-
- lt.table[7] = 24;
- lt.table[8] = 152;
- lt.table[9] = 112;
-
- for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
- for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
- for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
- for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
-
- /* build fixed distance tree */
- for (i = 0; i < 5; ++i) dt.table[i] = 0;
-
- dt.table[5] = 32;
-
- for (i = 0; i < 32; ++i) dt.trans[i] = i;
-}
-
-/* given an array of code lengths, build a tree */
-var offs = new Uint16Array(16);
-
-function tinf_build_tree(t, lengths, off, num) {
- var i, sum;
-
- /* clear code length count table */
- for (i = 0; i < 16; ++i) t.table[i] = 0;
-
- /* scan symbol lengths, and sum code length counts */
- for (i = 0; i < num; ++i) t.table[lengths[off + i]]++;
-
- t.table[0] = 0;
-
- /* compute offset table for distribution sort */
- for (sum = 0, i = 0; i < 16; ++i) {
- offs[i] = sum;
- sum += t.table[i];
- }
-
- /* create code->symbol translation table (symbols sorted by code) */
- for (i = 0; i < num; ++i) {
- if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i;
- }
-}
-
-/* ---------------------- *
- * -- decode functions -- *
- * ---------------------- */
-
-/* get one bit from source stream */
-function tinf_getbit(d) {
- /* check if tag is empty */
- if (!d.bitcount--) {
- /* load next tag */
- d.tag = d.source[d.sourceIndex++];
- d.bitcount = 7;
- }
-
- /* shift bit out of tag */
- var bit = d.tag & 1;
- d.tag >>>= 1;
-
- return bit;
-}
-
-/* read a num bit value from a stream and add base */
-function tinf_read_bits(d, num, base) {
- if (!num)
- return base;
-
- while (d.bitcount < 24) {
- d.tag |= d.source[d.sourceIndex++] << d.bitcount;
- d.bitcount += 8;
- }
-
- var val = d.tag & (0xffff >>> (16 - num));
- d.tag >>>= num;
- d.bitcount -= num;
- return val + base;
-}
-
-/* given a data stream and a tree, decode a symbol */
-function tinf_decode_symbol(d, t) {
- while (d.bitcount < 24) {
- d.tag |= d.source[d.sourceIndex++] << d.bitcount;
- d.bitcount += 8;
- }
-
- var sum = 0, cur = 0, len = 0;
- var tag = d.tag;
-
- /* get more bits while code value is above sum */
- do {
- cur = 2 * cur + (tag & 1);
- tag >>>= 1;
- ++len;
-
- sum += t.table[len];
- cur -= t.table[len];
- } while (cur >= 0);
-
- d.tag = tag;
- d.bitcount -= len;
-
- return t.trans[sum + cur];
-}
-
-/* given a data stream, decode dynamic trees from it */
-function tinf_decode_trees(d, lt, dt) {
- var hlit, hdist, hclen;
- var i, num, length;
-
- /* get 5 bits HLIT (257-286) */
- hlit = tinf_read_bits(d, 5, 257);
-
- /* get 5 bits HDIST (1-32) */
- hdist = tinf_read_bits(d, 5, 1);
-
- /* get 4 bits HCLEN (4-19) */
- hclen = tinf_read_bits(d, 4, 4);
-
- for (i = 0; i < 19; ++i) lengths[i] = 0;
-
- /* read code lengths for code length alphabet */
- for (i = 0; i < hclen; ++i) {
- /* get 3 bits code length (0-7) */
- var clen = tinf_read_bits(d, 3, 0);
- lengths[clcidx[i]] = clen;
- }
-
- /* build code length tree */
- tinf_build_tree(code_tree, lengths, 0, 19);
-
- /* decode code lengths for the dynamic trees */
- for (num = 0; num < hlit + hdist;) {
- var sym = tinf_decode_symbol(d, code_tree);
-
- switch (sym) {
- case 16:
- /* copy previous code length 3-6 times (read 2 bits) */
- var prev = lengths[num - 1];
- for (length = tinf_read_bits(d, 2, 3); length; --length) {
- lengths[num++] = prev;
- }
- break;
- case 17:
- /* repeat code length 0 for 3-10 times (read 3 bits) */
- for (length = tinf_read_bits(d, 3, 3); length; --length) {
- lengths[num++] = 0;
- }
- break;
- case 18:
- /* repeat code length 0 for 11-138 times (read 7 bits) */
- for (length = tinf_read_bits(d, 7, 11); length; --length) {
- lengths[num++] = 0;
- }
- break;
- default:
- /* values 0-15 represent the actual code lengths */
- lengths[num++] = sym;
- break;
- }
- }
-
- /* build dynamic trees */
- tinf_build_tree(lt, lengths, 0, hlit);
- tinf_build_tree(dt, lengths, hlit, hdist);
-}
-
-/* ----------------------------- *
- * -- block inflate functions -- *
- * ----------------------------- */
-
-/* given a stream and two trees, inflate a block of data */
-function tinf_inflate_block_data(d, lt, dt) {
- while (1) {
- var sym = tinf_decode_symbol(d, lt);
-
- /* check for end of block */
- if (sym === 256) {
- return TINF_OK;
- }
-
- if (sym < 256) {
- d.dest[d.destLen++] = sym;
- } else {
- var length, dist, offs;
- var i;
-
- sym -= 257;
-
- /* possibly get more bits from length code */
- length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
-
- dist = tinf_decode_symbol(d, dt);
-
- /* possibly get more bits from distance code */
- offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
-
- /* copy match */
- for (i = offs; i < offs + length; ++i) {
- d.dest[d.destLen++] = d.dest[i];
- }
- }
- }
-}
-
-/* inflate an uncompressed block of data */
-function tinf_inflate_uncompressed_block(d) {
- var length, invlength;
- var i;
-
- /* unread from bitbuffer */
- while (d.bitcount > 8) {
- d.sourceIndex--;
- d.bitcount -= 8;
- }
-
- /* get length */
- length = d.source[d.sourceIndex + 1];
- length = 256 * length + d.source[d.sourceIndex];
-
- /* get one's complement of length */
- invlength = d.source[d.sourceIndex + 3];
- invlength = 256 * invlength + d.source[d.sourceIndex + 2];
-
- /* check length */
- if (length !== (~invlength & 0x0000ffff))
- return TINF_DATA_ERROR;
-
- d.sourceIndex += 4;
-
- /* copy block */
- for (i = length; i; --i)
- d.dest[d.destLen++] = d.source[d.sourceIndex++];
-
- /* make sure we start next block on a byte boundary */
- d.bitcount = 0;
-
- return TINF_OK;
-}
-
-/* inflate stream from source to dest */
-function tinf_uncompress(source, dest) {
- var d = new Data(source, dest);
- var bfinal, btype, res;
-
- do {
- /* read final block flag */
- bfinal = tinf_getbit(d);
-
- /* read block type (2 bits) */
- btype = tinf_read_bits(d, 2, 0);
-
- /* decompress block */
- switch (btype) {
- case 0:
- /* decompress uncompressed block */
- res = tinf_inflate_uncompressed_block(d);
- break;
- case 1:
- /* decompress block with fixed huffman trees */
- res = tinf_inflate_block_data(d, sltree, sdtree);
- break;
- case 2:
- /* decompress block with dynamic huffman trees */
- tinf_decode_trees(d, d.ltree, d.dtree);
- res = tinf_inflate_block_data(d, d.ltree, d.dtree);
- break;
- default:
- res = TINF_DATA_ERROR;
- }
-
- if (res !== TINF_OK)
- throw new Error('Data error');
-
- } while (!bfinal);
-
- if (d.destLen < d.dest.length) {
- if (typeof d.dest.slice === 'function')
- return d.dest.slice(0, d.destLen);
- else
- return d.dest.subarray(0, d.destLen);
- }
-
- return d.dest;
-}
-
-/* -------------------- *
- * -- initialization -- *
- * -------------------- */
-
-/* build fixed huffman trees */
-tinf_build_fixed_trees(sltree, sdtree);
-
-/* build extra bits and base tables */
-tinf_build_bits_base(length_bits, length_base, 4, 3);
-tinf_build_bits_base(dist_bits, dist_base, 2, 1);
-
-/* fix a special case */
-length_bits[28] = 0;
-length_base[28] = 258;
-
-module.exports = tinf_uncompress;
-
- return module.exports
-})()
-// End tinyInflate
-
-// Begin woff2otf.js
-/*
- Copyright 2012, Steffen Hanikel (https://github.com/hanikesn)
- Modified by Artemy Tregubenko, 2014 (https://github.com/arty-name/woff2otf)
- Modified by Jason Johnston, 2019 (pako --> tiny-inflate)
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- A tool to convert a WOFF back to a TTF/OTF font file, in pure Javascript
+/*!
+Custom bundle of woff2otf (https://github.com/arty-name/woff2otf) with tiny-inflate
+(https://github.com/foliojs/tiny-inflate) for use in Troika text rendering.
+Original licenses apply:
+- tiny-inflate: https://github.com/foliojs/tiny-inflate/blob/master/LICENSE (MIT)
+- woff2otf.js: https://github.com/arty-name/woff2otf/blob/master/woff2otf.js (Apache2)
*/
-
-function convert_streams(bufferIn, tinyInflate) {
- var dataViewIn = new DataView(bufferIn);
- var offsetIn = 0;
-
- function read2() {
- var uint16 = dataViewIn.getUint16(offsetIn);
- offsetIn += 2;
- return uint16;
- }
-
- function read4() {
- var uint32 = dataViewIn.getUint32(offsetIn);
- offsetIn += 4;
- return uint32;
- }
-
- function write2(uint16) {
- dataViewOut.setUint16(offsetOut, uint16);
- offsetOut += 2;
- }
-
- function write4(uint32) {
- dataViewOut.setUint32(offsetOut, uint32);
- offsetOut += 4;
- }
-
- var WOFFHeader = {
- signature: read4(),
- flavor: read4(),
- length: read4(),
- numTables: read2(),
- reserved: read2(),
- totalSfntSize: read4(),
- majorVersion: read2(),
- minorVersion: read2(),
- metaOffset: read4(),
- metaLength: read4(),
- metaOrigLength: read4(),
- privOffset: read4(),
- privLength: read4()
- };
-
- var entrySelector = 0;
- while (Math.pow(2, entrySelector) <= WOFFHeader.numTables) {
- entrySelector++;
- }
- entrySelector--;
-
- var searchRange = Math.pow(2, entrySelector) * 16;
- var rangeShift = WOFFHeader.numTables * 16 - searchRange;
-
- var offset = 4 + 2 + 2 + 2 + 2;
- var TableDirectoryEntries = [];
- for (var i = 0; i < WOFFHeader.numTables; i++) {
- TableDirectoryEntries.push({
- tag: read4(),
- offset: read4(),
- compLength: read4(),
- origLength: read4(),
- origChecksum: read4()
- });
- offset += 4 * 4;
- }
-
- var arrayOut = new Uint8Array(
- 4 + 2 + 2 + 2 + 2 +
- TableDirectoryEntries.length * (4 + 4 + 4 + 4) +
- TableDirectoryEntries.reduce(function(acc, entry) { return acc + entry.origLength + 4; }, 0)
- );
- var bufferOut = arrayOut.buffer;
- var dataViewOut = new DataView(bufferOut);
- var offsetOut = 0;
-
- write4(WOFFHeader.flavor);
- write2(WOFFHeader.numTables);
- write2(searchRange);
- write2(entrySelector);
- write2(rangeShift);
-
- TableDirectoryEntries.forEach(function(TableDirectoryEntry) {
- write4(TableDirectoryEntry.tag);
- write4(TableDirectoryEntry.origChecksum);
- write4(offset);
- write4(TableDirectoryEntry.origLength);
-
- TableDirectoryEntry.outOffset = offset;
- offset += TableDirectoryEntry.origLength;
- if ((offset % 4) != 0) {
- offset += 4 - (offset % 4)
- }
- });
-
- var size;
-
- TableDirectoryEntries.forEach(function(TableDirectoryEntry) {
- var compressedData = bufferIn.slice(
- TableDirectoryEntry.offset,
- TableDirectoryEntry.offset + TableDirectoryEntry.compLength
- );
-
- if (TableDirectoryEntry.compLength != TableDirectoryEntry.origLength) {
- var uncompressedData = new Uint8Array(TableDirectoryEntry.origLength)
- tinyInflate(
- new Uint8Array(compressedData, 2), //skip deflate header
- uncompressedData
- )
- } else {
- uncompressedData = new Uint8Array(compressedData);
- }
-
- arrayOut.set(uncompressedData, TableDirectoryEntry.outOffset);
- offset = TableDirectoryEntry.outOffset + TableDirectoryEntry.origLength;
-
- var padding = 0;
- if ((offset % 4) != 0) {
- padding = 4 - (offset % 4);
- }
- arrayOut.set(
- new Uint8Array(padding).buffer,
- TableDirectoryEntry.outOffset + TableDirectoryEntry.origLength
- );
-
- size = offset + padding;
- });
-
- return bufferOut.slice(0, size);
-}
-
-// End woff2otf.js
-
-return function(buffer) {
- return convert_streams(buffer, tinyInflate)
-}
-
-}
+export default function(){return function(t){"use strict";function e(){this.table=new Uint16Array(16),this.trans=new Uint16Array(288)}function r(t,r){this.source=t,this.sourceIndex=0,this.tag=0,this.bitcount=0,this.dest=r,this.destLen=0,this.ltree=new e,this.dtree=new e}var n=new e,o=new e,a=new Uint8Array(30),s=new Uint16Array(30),i=new Uint8Array(30),u=new Uint16Array(30),f=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),c=new e,g=new Uint8Array(320);function b(t,e,r,n){var o,a;for(o=0;o