From 43144cfbb8f553d552a5bef179a7e5cfc8179fe3 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Thu, 3 Dec 2020 17:24:05 -0700 Subject: [PATCH] feat(troika-three-text): fix kerning by updating from Typr.js to Typr.ts Fixes #70. Our old Typr.js had broken kerning, and has since moved to a Harfbuzz approach which removed base support for some features like ligatures. Thankfully fredli74's Typr.ts fork maintains those and has fixed the kerning bug. This commit updates the Typr build to use that fork, and adds kerning calculation to FontParser_Typr.js. --- package.json | 1 + packages/troika-examples/text/TextExample.jsx | 2 +- packages/troika-three-text/README.md | 2 +- packages/troika-three-text/build-typr.js | 86 - .../troika-three-text/libs/typr.factory.js | 2674 +---------------- .../libs/woff2otf.factory.js | 554 +--- packages/troika-three-text/package.json | 7 +- .../rollup.config.build-typr.js | 56 + .../rollup.config.build-woff2otf.js | 53 + packages/troika-three-text/src/woff2otf.js | 6 +- .../src/worker/FontParser_Typr.js | 10 +- 11 files changed, 142 insertions(+), 3309 deletions(-) delete mode 100644 packages/troika-three-text/build-typr.js create mode 100644 packages/troika-three-text/rollup.config.build-typr.js create mode 100644 packages/troika-three-text/rollup.config.build-woff2otf.js 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.

) diff --git a/packages/troika-three-text/README.md b/packages/troika-three-text/README.md index 986b42a7..ff465b77 100644 --- a/packages/troika-three-text/README.md +++ b/packages/troika-three-text/README.md @@ -2,7 +2,7 @@ This package provides high quality text rendering in [Three.js](https://threejs.org) scenes, using signed distance fields (SDF) and antialiasing using standard derivatives. -Rather than relying on pre-generated SDF textures, this parses font files (.ttf, .otf, .woff) directly using [Typr.js](https://github.com/photopea/Typr.js), and generates the SDF atlas for glyphs on-the-fly as they are used. It also handles proper kerning and ligature glyph substitution. All font parsing, SDF generation, and glyph layout is performed in a web worker to prevent frame drops. +Rather than relying on pre-generated SDF textures, this parses font files (.ttf, .otf, .woff) directly using [Typr](https://github.com/fredli74/Typr.ts), and generates the SDF atlas for glyphs on-the-fly as they are used. It also handles proper kerning and ligature glyph substitution. All font parsing, SDF generation, and glyph layout is performed in a web worker to prevent frame drops. Once the SDFs are generated, it assembles a geometry that positions all the glyphs, and _patches_ any Three.js Material with the proper shader code for rendering the SDFs. This means you can still benefit from all the features of Three.js's built-in materials like lighting, physically-based rendering, shadows, and fog. diff --git a/packages/troika-three-text/build-typr.js b/packages/troika-three-text/build-typr.js deleted file mode 100644 index 2bff1f73..00000000 --- a/packages/troika-three-text/build-typr.js +++ /dev/null @@ -1,86 +0,0 @@ -// This build file creates a static version of the Typr.js library used for -// processing fonts. It's isolated within a "factory" function wrapper so it can -// easily be marshalled into a web worker. - -const fetch = require('node-fetch') -const fs = require('fs') - -async function fetchText(url) { - const response = await fetch(url) - if (response.ok) { - const text = await response.text() - return text.replace(/\r\n/g, '\n') - } else { - throw new Error(response.statusText) - } -} - -async function buildTypr() { - // NOTE: probably want to lock this to the last version of Typr before they stripped out - // built-in support for some things like ligatures in favor of a much larger Harfbuzz wasm - // plugin. We may need to fork Typr going forward if we need to merge future fixes. - const typr = await fetchText('https://raw.githubusercontent.com/photopea/Typr.js/gh-pages/src/Typr.js') - let typrU = await fetchText('https://raw.githubusercontent.com/photopea/Typr.js/gh-pages/src/Typr.U.js') - - const output = ` -// Custom bundle of Typr.js (https://github.com/photopea/Typr.js) for use in troika-3d-text. -// Original MIT license applies: https://github.com/photopea/Typr.js/blob/gh-pages/LICENSE - -export default function() { - -const window = self - -// Begin Typr.js -${typr} -// End Typr.js - -// Begin Typr.U.js -${typrU} -// End Typr.U.js - -return Typr - -} -` - - fs.writeFileSync('libs/typr.factory.js', output) -} - - -async function buildWoff2otf() { - const tinyInflate = await fetchText('https://raw.githubusercontent.com/foliojs/tiny-inflate/master/index.js') - const woff2otf = fs.readFileSync('src/woff2otf.js') - - const output = ` -// Custom bundle of woff2otf (https://github.com/arty-name/woff2otf) with tiny-inflate -// (https://github.com/foliojs/tiny-inflate) for use in troika-3d-text. -// 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) - -export default function() { - -// Begin tinyInflate -const tinyInflate = (function() { - const module = {} - ${tinyInflate} - return module.exports -})() -// End tinyInflate - -// Begin woff2otf.js -${woff2otf} -// End woff2otf.js - -return function(buffer) { - return convert_streams(buffer, tinyInflate) -} - -} -` - - fs.writeFileSync('libs/woff2otf.factory.js', output) -} - -buildTypr() -buildWoff2otf() diff --git a/packages/troika-three-text/libs/typr.factory.js b/packages/troika-three-text/libs/typr.factory.js index 634c6c0c..73d9f16e 100644 --- a/packages/troika-three-text/libs/typr.factory.js +++ b/packages/troika-three-text/libs/typr.factory.js @@ -1,2669 +1,5 @@ - -// Custom bundle of Typr.js (https://github.com/photopea/Typr.js) for use in troika-3d-text. -// Original MIT license applies: https://github.com/photopea/Typr.js/blob/gh-pages/LICENSE - -export default function() { - -const window = self - -// Begin Typr.js - - -var Typr = {}; - -Typr.parse = function(buff) -{ - var bin = Typr._bin; - var data = new Uint8Array(buff); - - var tag = bin.readASCII(data, 0, 4); - if(tag=="ttcf") { - var offset = 4; - var majV = bin.readUshort(data, offset); offset+=2; - var minV = bin.readUshort(data, offset); offset+=2; - var numF = bin.readUint (data, offset); offset+=4; - var fnts = []; - for(var i=0; i=buff.length) throw "error"; - var a = Typr._bin.t.uint8; - a[0] = buff[p+3]; - a[1] = buff[p+2]; - a[2] = buff[p+1]; - a[3] = buff[p]; - return Typr._bin.t.int32[0]; - }, - - readInt8 : function(buff, p) - { - //if(p>=buff.length) throw "error"; - var a = Typr._bin.t.uint8; - a[0] = buff[p]; - return Typr._bin.t.int8[0]; - }, - readShort : function(buff, p) - { - //if(p>=buff.length) throw "error"; - var a = Typr._bin.t.uint8; - a[1] = buff[p]; a[0] = buff[p+1]; - return Typr._bin.t.int16[0]; - }, - readUshort : function(buff, p) - { - //if(p>=buff.length) throw "error"; - return (buff[p]<<8) | buff[p+1]; - }, - readUshorts : function(buff, p, len) - { - var arr = []; - for(var i=0; i=buff.length) throw "error"; - var a = Typr._bin.t.uint8; - a[3] = buff[p]; a[2] = buff[p+1]; a[1] = buff[p+2]; a[0] = buff[p+3]; - return Typr._bin.t.uint32[0]; - }, - readUint64 : function(buff, p) - { - //if(p>=buff.length) throw "error"; - return (Typr._bin.readUint(buff, p)*(0xffffffff+1)) + Typr._bin.readUint(buff, p+4); - }, - readASCII : function(buff, p, l) // l : length in Characters (not Bytes) - { - //if(p>=buff.length) throw "error"; - var s = ""; - for(var i = 0; i < l; i++) s += String.fromCharCode(buff[p+i]); - return s; - }, - readUnicode : function(buff, p, l) - { - //if(p>=buff.length) throw "error"; - var s = ""; - for(var i = 0; i < l; i++) - { - var c = (buff[p++]<<8) | buff[p++]; - s += String.fromCharCode(c); - } - return s; - }, - _tdec : window["TextDecoder"] ? new window["TextDecoder"]() : null, - readUTF8 : function(buff, p, l) { - var tdec = Typr._bin._tdec; - if(tdec && p==0 && l==buff.length) return tdec["decode"](buff); - return Typr._bin.readASCII(buff,p,l); - }, - readBytes : function(buff, p, l) - { - //if(p>=buff.length) throw "error"; - var arr = []; - for(var i=0; i=buff.length) throw "error"; - var s = []; - for(var i = 0; i < l; i++) - s.push(String.fromCharCode(buff[p+i])); - return s; - } -}; - -Typr._bin.t = { - buff: new ArrayBuffer(8), -}; -Typr._bin.t.int8 = new Int8Array (Typr._bin.t.buff); -Typr._bin.t.uint8 = new Uint8Array (Typr._bin.t.buff); -Typr._bin.t.int16 = new Int16Array (Typr._bin.t.buff); -Typr._bin.t.uint16 = new Uint16Array(Typr._bin.t.buff); -Typr._bin.t.int32 = new Int32Array (Typr._bin.t.buff); -Typr._bin.t.uint32 = new Uint32Array(Typr._bin.t.buff); - - - - - -// OpenType Layout Common Table Formats - -Typr._lctf = {}; - -Typr._lctf.parse = function(data, offset, length, font, subt) -{ - var bin = Typr._bin; - var obj = {}; - var offset0 = offset; - var tableVersion = bin.readFixed(data, offset); offset += 4; - - var offScriptList = bin.readUshort(data, offset); offset += 2; - var offFeatureList = bin.readUshort(data, offset); offset += 2; - var offLookupList = bin.readUshort(data, offset); offset += 2; - - - obj.scriptList = Typr._lctf.readScriptList (data, offset0 + offScriptList); - obj.featureList = Typr._lctf.readFeatureList(data, offset0 + offFeatureList); - obj.lookupList = Typr._lctf.readLookupList (data, offset0 + offLookupList, subt); - - return obj; -} - -Typr._lctf.readLookupList = function(data, offset, subt) -{ - var bin = Typr._bin; - var offset0 = offset; - var obj = []; - var count = bin.readUshort(data, offset); offset+=2; - for(var i=0; i>>i)&1) != 0) num++; - return num; -} - -Typr._lctf.readClassDef = function(data, offset) -{ - var bin = Typr._bin; - var obj = []; - var format = bin.readUshort(data, offset); offset+=2; - if(format==1) - { - var startGlyph = bin.readUshort(data, offset); offset+=2; - var glyphCount = bin.readUshort(data, offset); offset+=2; - for(var i=0; i 255 ) return -1; - return Typr.CFF.glyphByUnicode(cff, Typr.CFF.tableSE[charcode]); - } - - Typr.CFF.readEncoding = function(data, offset, num) - { - var bin = Typr._bin; - - var array = ['.notdef']; - var format = data[offset]; offset++; - //console.log("Encoding"); - //console.log(format); - - if(format==0) - { - var nCodes = data[offset]; offset++; - for(var i=0; i>4, nib1 = b&0xf; - if(nib0 != 0xf) nibs.push(nib0); if(nib1!=0xf) nibs.push(nib1); - if(nib1==0xf) break; - } - var s = ""; - var chars = [0,1,2,3,4,5,6,7,8,9,".","e","e-","reserved","-","endOfNumber"]; - for(var i=0; i=gl.xMax || gl.yMin>=gl.yMax) return null; - - if(gl.noc>0) - { - gl.endPts = []; - for(var i=0; i>>8; - /* I have seen format 128 once, that's why I do */ format &= 0xf; - if(format==0) offset = Typr.kern.readFormat0(data, offset, map); - else throw "unknown kern table format: "+format; - } - return map; -} - -Typr.kern.parseV1 = function(data, offset, length, font) -{ - var bin = Typr._bin; - - var version = bin.readFixed(data, offset); offset+=4; - var nTables = bin.readUint(data, offset); offset+=4; - - var map = {glyph1: [], rval:[]}; - for(var i=0; i>>8; - /* I have seen format 128 once, that's why I do */ format &= 0xf; - if(format==0) offset = Typr.kern.readFormat0(data, offset, map); - else throw "unknown kern table format: "+format; - } - return map; -} - -Typr.kern.readFormat0 = function(data, offset, map) -{ - var bin = Typr._bin; - var pleft = -1; - var nPairs = bin.readUshort(data, offset); offset+=2; - var searchRange = bin.readUshort(data, offset); offset+=2; - var entrySelector = bin.readUshort(data, offset); offset+=2; - var rangeShift = bin.readUshort(data, offset); offset+=2; - for(var j=0; j=tab.map.length) return 0; - return tab.map[code]; - } - else if(tab.format==4) - { - var sind = -1; - for(var i=0; icode) return 0; - - var gli = 0; - if(tab.idRangeOffset[sind]!=0) gli = tab.glyphIdArray[(code-tab.startCount[sind]) + (tab.idRangeOffset[sind]>>1) - (tab.idRangeOffset.length-sind)]; - else gli = code + tab.idDelta[sind]; - return gli & 0xFFFF; - } - else if(tab.format==12) - { - if(code>tab.groups[tab.groups.length-1][1]) return 0; - for(var i=0; i-1) Typr.U._simpleGlyph(gl, path); - else Typr.U._compoGlyph (gl, font, path); - } -} -Typr.U._simpleGlyph = function(gl, p) -{ - for(var c=0; c=g) return cd.class[i]; - //return 0; -} - -Typr.U.getPairAdjustment = function(font, g1, g2) -{ - //return 0; - if(font.GPOS) { - var gpos = font["GPOS"]; - var llist = gpos.lookupList, flist = gpos.featureList; - var tused = []; - for(var i=0; i0xffff) i++; - gls.push(Typr.U.codeToGlyph(font, cc)); - } - for(var i=0; i0xffff) i++; - } - //console.log(gls.slice(0)); - - //console.log(gls); return gls; - - var gsub = font["GSUB"]; if(gsub==null) return gls; - var llist = gsub.lookupList, flist = gsub.featureList; - - var cligs = ["rlig", "liga", "mset", "isol","init","fina","medi", "half", "pres", - "blws" /* Tibetan fonts like Himalaya.ttf */ ]; - - //console.log(gls.slice(0)); - var tused = []; - for(var fi=0; firlim) continue; - var good = true, em1 = 0; - for(var l=0; lrlim) continue; - var good = true; - for(var l=0; l> 1; - stack.length = 0; - haveWidth = true; - } - else if(v=="o3" || v=="o23") // vstem || vstemhm - { - var hasWidthArg; - - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + pdct.nominalWidthX; - } - - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; - } - else if(v=="o4") - { - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + pdct.nominalWidthX; - haveWidth = true; - } - if(open) Typr.U.P.closePath(p); - - y += stack.pop(); - Typr.U.P.moveTo(p,x,y); open=true; - } - else if(v=="o5") - { - while (stack.length > 0) { - x += stack.shift(); - y += stack.shift(); - Typr.U.P.lineTo(p, x, y); - } - } - else if(v=="o6" || v=="o7") // hlineto || vlineto - { - var count = stack.length; - var isX = (v == "o6"); - - for(var j=0; j Math.abs(c4y - y)) { - x = c4x + stack.shift(); - } else { - y = c4y + stack.shift(); - } - Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); - Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y); - } - } - else if(v=="o14") - { - if (stack.length > 0 && !haveWidth) { - width = stack.shift() + font.nominalWidthX; - haveWidth = true; - } - if(stack.length==4) // seac = standard encoding accented character - { - - var asb = 0; - var adx = stack.shift(); - var ady = stack.shift(); - var bchar = stack.shift(); - var achar = stack.shift(); - - - var bind = Typr.CFF.glyphBySE(font, bchar); - var aind = Typr.CFF.glyphBySE(font, achar); - - //console.log(bchar, bind); - //console.log(achar, aind); - //state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width; state.open=open; - - Typr.U._drawCFF(font.CharStrings[bind], state,font,pdct,p); - state.x = adx; state.y = ady; - Typr.U._drawCFF(font.CharStrings[aind], state,font,pdct,p); - - //x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width; open=state.open; - } - if(open) { Typr.U.P.closePath(p); open=false; } - } - else if(v=="o19" || v=="o20") - { - var hasWidthArg; - - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + pdct.nominalWidthX; - } - - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; - - i += (nStems + 7) >> 3; - } - - else if(v=="o21") { - if (stack.length > 2 && !haveWidth) { - width = stack.shift() + pdct.nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - x += stack.pop(); - - if(open) Typr.U.P.closePath(p); - Typr.U.P.moveTo(p,x,y); open=true; - } - else if(v=="o22") - { - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + pdct.nominalWidthX; - haveWidth = true; - } - - x += stack.pop(); - - if(open) Typr.U.P.closePath(p); - Typr.U.P.moveTo(p,x,y); open=true; - } - else if(v=="o25") - { - while (stack.length > 6) { - x += stack.shift(); - y += stack.shift(); - Typr.U.P.lineTo(p, x, y); - } - - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); - } - else if(v=="o26") - { - if (stack.length % 2) { - x += stack.shift(); - } - - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x; - y = c2y + stack.shift(); - Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); - } - - } - else if(v=="o27") - { - if (stack.length % 2) { - y += stack.shift(); - } - - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y; - Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); - } - } - else if(v=="o10" || v=="o29") // callsubr || callgsubr - { - var obj = (v=="o10" ? pdct : font); - if(stack.length==0) { console.log("error: empty stack"); } - else { - var ind = stack.pop(); - var subr = obj.Subrs[ ind + obj.Bias ]; - state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width; state.open=open; - Typr.U._drawCFF(subr, state,font,pdct,p); - x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width; open=state.open; - } - } - else if(v=="o30" || v=="o31") // vhcurveto || hvcurveto - { - var count, count1 = stack.length; - var index = 0; - var alternate = v == "o31"; - - count = count1 & ~2; - index += count1 - count; - - while ( index < count ) - { - if(alternate) - { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - if(count-index == 5) { x = c2x + stack.shift(); index++; } - else x = c2x; - alternate = false; - } - else - { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - if(count-index == 5) { y = c2y + stack.shift(); index++; } - else y = c2y; - alternate = true; - } - Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); - index += 4; - } - } - - else if((v+"").charAt(0)=="o") { console.log("Unknown operation: "+v, cmds); throw v; } - else stack.push(v); - } - //console.log(cmds); - state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width; state.open=open; -} - -// End Typr.U.js - -return Typr - -} +/*! +Custom build of Typr.ts (https://github.com/fredli74/Typr.ts) for use in Troika text rendering. +Original MIT license applies: https://github.com/fredli74/Typr.ts/blob/master/LICENSE +*/ +export default function(){return"undefined"==typeof window&&(self.window=self),function(r){"use strict";var e={parse:function(r){var t=e._bin,a=new Uint8Array(r);if("ttcf"==t.readASCII(a,0,4)){var n=4;t.readUshort(a,n);n+=2;t.readUshort(a,n);n+=2;var o=t.readUint(a,n);n+=4;for(var s=[],i=0;i>>t&1)&&e++;return e},e._lctf.readClassDef=function(r,t){var a=e._bin,n=[],o=a.readUshort(r,t);if(t+=2,1==o){var s=a.readUshort(r,t);t+=2;var i=a.readUshort(r,t);t+=2;for(var h=0;h0&&(o.featureParams=n+s);var i=a.readUshort(r,t);t+=2,o.tab=[];for(var h=0;h255?-1:e.CFF.glyphByUnicode(r,e.CFF.tableSE[t])},e.CFF.readEncoding=function(r,t,a){e._bin;var n=[".notdef"],o=r[t];if(t++,0!=o)throw"error: unknown encoding format: "+o;var s=r[t];t++;for(var i=0;i>4,p=15&v;if(15!=c&&u.push(c),15!=p&&u.push(p),15==p)break}for(var U="",g=[0,1,2,3,4,5,6,7,8,9,".","e","e-","reserved","-","endOfNumber"],S=0;S=s.xMax||s.yMin>=s.yMax)return null;if(s.noc>0){s.endPts=[];for(var i=0;i=1&&i.fmt<=2){f=o.readUshort(r,a);a+=2;var l=o.readUshort(r,a);a+=2;d=e._lctf.numOfOnes(f);var u=e._lctf.numOfOnes(l);if(1==i.fmt){i.pairsets=[];var v=o.readUshort(r,a);a+=2;for(var c=0;c=1&&i.fmt<=2){if(1==i.fmt)i.delta=o.readShort(r,a),a+=2;else if(2==i.fmt){var f=o.readUshort(r,a);a+=2,i.newg=o.readUshorts(r,a,f),a+=2*i.newg.length}}else if(4==t){i.vals=[];f=o.readUshort(r,a);a+=2;for(var d=0;d>>8;if(0!=(l&=15))throw"unknown kern table format: "+l;t=e.kern.readFormat0(r,t,h)}return h},e.kern.parseV1=function(r,t,a,n){var o=e._bin;o.readFixed(r,t);t+=4;var s=o.readUint(r,t);t+=4;for(var i={glyph1:[],rval:[]},h=0;h>>8;if(0!=(d&=15))throw"unknown kern table format: "+d;t=e.kern.readFormat0(r,t,i)}return i},e.kern.readFormat0=function(r,t,a){var n=e._bin,o=-1,s=n.readUshort(r,t);t+=2;n.readUshort(r,t);t+=2;n.readUshort(r,t);t+=2;n.readUshort(r,t);t+=2;for(var i=0;i=n.map.length?0:n.map[e];if(4==n.format){for(var o=-1,s=0;se)return 0;return 65535&(0!=n.idRangeOffset[o]?n.glyphIdArray[e-n.startCount[o]+(n.idRangeOffset[o]>>1)-(n.idRangeOffset.length-o)]:e+n.idDelta[o])}if(12==n.format){if(e>n.groups[n.groups.length-1][1])return 0;for(s=0;s-1?e.U._simpleGlyph(n,a):e.U._compoGlyph(n,t,a))},e.U._simpleGlyph=function(r,t){for(var a=0;a65535&&n++,a.push(e.U.codeToGlyph(r,o))}for(n=0;n65535&&n++}var i=r.GSUB;if(null==i)return a;for(var h=i.lookupList,f=i.featureList,d=["rlig","liga","mset","isol","init","fina","medi","half","pres","blws"],l=[],u=0;uo)){for(var v=!0,c=0,p=0;po)){for(v=!0,p=0;p>1,s.length=0,h=!0;else if("o3"==x||"o23"==x){s.length%2!=0&&!h&&(f=s.shift()+n.nominalWidthX),i+=s.length>>1,s.length=0,h=!0}else if("o4"==x)s.length>1&&!h&&(f=s.shift()+n.nominalWidthX,h=!0),d&&e.U.P.closePath(o),v+=s.pop(),e.U.P.moveTo(o,u,v),d=!0;else if("o5"==x)for(;s.length>0;)u+=s.shift(),v+=s.shift(),e.U.P.lineTo(o,u,v);else if("o6"==x||"o7"==x)for(var P=s.length,w="o6"==x,I=0;IMath.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>>=1,e}function v(t,e,r){if(!e)return r;for(;t.bitcount<24;)t.tag|=t.source[t.sourceIndex++]<>>16-e;return t.tag>>>=e,t.bitcount-=e,n+r}function w(t,e){for(;t.bitcount<24;)t.tag|=t.source[t.sourceIndex++]<>>=1,++o,r+=e.table[o],n-=e.table[o]}while(n>=0);return t.tag=a,t.bitcount-=o,e.trans[r+n]}function L(t,e,r){var n,o,a,s,i,u;for(n=v(t,5,257),o=v(t,5,1),a=v(t,4,4),s=0;s<19;++s)g[s]=0;for(s=0;s8;)t.sourceIndex--,t.bitcount-=8;if((e=256*(e=t.source[t.sourceIndex+1])+t.source[t.sourceIndex])!==(65535&~(256*t.source[t.sourceIndex+3]+t.source[t.sourceIndex+2])))return-3;for(t.sourceIndex+=4,r=e;r;--r)t.dest[t.destLen++]=t.source[t.sourceIndex++];return t.bitcount=0,0}!function(t,e){var r;for(r=0;r<7;++r)t.table[r]=0;for(t.table[7]=24,t.table[8]=152,t.table[9]=112,r=0;r<24;++r)t.trans[r]=256+r;for(r=0;r<144;++r)t.trans[24+r]=r;for(r=0;r<8;++r)t.trans[168+r]=280+r;for(r=0;r<112;++r)t.trans[176+r]=144+r;for(r=0;r<5;++r)e.table[r]=0;for(e.table[5]=32,r=0;r<32;++r)e.trans[r]=r}(n,o),b(a,s,4,3),b(i,u,2,1),a[28]=0,s[28]=258;var y=function(t,e){var a,s,i=new r(t,e);do{switch(a=l(i),v(i,2,0)){case 0:s=m(i);break;case 1:s=U(i,n,o);break;case 2:L(i,i.ltree,i.dtree),s=U(i,i.ltree,i.dtree);break;default:s=-3}if(0!==s)throw new Error("Data error")}while(!a);return i.destLen 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 @@ -18,7 +18,9 @@ A tool to convert a WOFF back to a TTF/OTF font file, in pure Javascript */ -function convert_streams(bufferIn, tinyInflate) { +import tinyInflate from 'tiny-inflate' + +export function convert_streams(bufferIn) { var dataViewIn = new DataView(bufferIn); var offsetIn = 0; diff --git a/packages/troika-three-text/src/worker/FontParser_Typr.js b/packages/troika-three-text/src/worker/FontParser_Typr.js index 56605d50..51d1f486 100644 --- a/packages/troika-three-text/src/worker/FontParser_Typr.js +++ b/packages/troika-three-text/src/worker/FontParser_Typr.js @@ -29,7 +29,8 @@ function parserFactory(Typr, woff2otf) { const glyphIndices = Typr.U.stringToGlyphs(typrFont, text) let charIndex = 0 - glyphIndices.forEach(glyphIndex => { + let prevGlyphIndex = -1 + glyphIndices.forEach((glyphIndex, i) => { // Typr returns a glyph index per string codepoint, with -1s in place of those that // were omitted due to ligature substitution. So we can track original index in the // string via simple increment, and skip everything else when seeing a -1. @@ -80,6 +81,11 @@ function parserFactory(Typr, woff2otf) { } } + // Kerning + if (prevGlyphIndex !== -1) { + glyphX += Typr.U.getPairAdjustment(typrFont, prevGlyphIndex, glyphIndex) * fontScale + } + callback.call(null, glyphObj, glyphX, charIndex) if (glyphObj.advanceWidth) { @@ -88,6 +94,8 @@ function parserFactory(Typr, woff2otf) { if (letterSpacing) { glyphX += letterSpacing * fontSize } + + prevGlyphIndex = glyphIndex } charIndex += (text.codePointAt(charIndex) > 0xffff ? 2 : 1) })