import { inflate } from 'pako';

// Mocking the pako module to just have inflate for a smaller package size
const pako = { inflate };

var Typr = {};

Typr["parse"] = function (buff) {
  var bin = Typr["B"];

  var readFont = function (data, idx, offset, tmap) {
    var T = Typr["T"];
    var prsr = {
      "cmap": T.cmap,
      "head": T.head,
      "hhea": T.hhea,
      "maxp": T.maxp,
      "hmtx": T.hmtx,
      "name": T.name,
      "OS/2": T.OS2,
      "post": T.post,

      "loca": T.loca,
      "kern": T.kern,
      "glyf": T.glyf,

      "CFF ": T.CFF,
      /*
      "GPOS",
      "GSUB",
      "GDEF",*/
      "GSUB": T.GSUB,
      "CBLC": T.CBLC,
      "CBDT": T.CBDT,

      "SVG ": T.SVG,
      "COLR": T.colr,
      "CPAL": T.cpal,
      "sbix": T.sbix,

      "fvar": T.fvar,
      "gvar": T.gvar,
      "avar": T.avar,
      "HVAR": T.HVAR
      //"VORG",
    };
    var obj = { "_data": data, "_index": idx, "_offset": offset };

    for (var t in prsr) {
      var tab = Typr["findTable"](data, t, offset);
      if (tab) {
        var off = tab[0], tobj = tmap[off];
        if (tobj == null) tobj = prsr[t].parseTab(data, off, tab[1], obj);
        obj[t] = tmap[off] = tobj;
      }
    }
    return obj;
  };

  function woffToOtf(data) {
    var numTables = bin.readUshort(data, 12);
    var totalSize = bin.readUint(data, 16);

    var otf = new Uint8Array(totalSize), toff = 12 + numTables * 16;

    bin.writeASCII(otf, 0, "OTTO");
    bin.writeUshort(otf, 4, numTables);

    var off = 44;
    for (var i = 0; i < numTables; i++) {
      var tag = bin.readASCII(data, off, 4);
      var tof = bin.readUint(data, off + 4);
      var cLe = bin.readUint(data, off + 8);
      var oLe = bin.readUint(data, off + 12);
      off += 20;
      //console.log(i, ":::", tag,tof,oLe);

      var tab = data.slice(tof, tof + cLe);
      if (cLe != oLe) tab = pako["inflate"](tab);

      var to = 12 + i * 16;
      bin.writeASCII(otf, to, tag);
      bin.writeUint(otf, to + 8, toff);
      bin.writeUint(otf, to + 12, oLe);

      otf.set(tab, toff); toff += oLe;
    }
    //console.log(otf);
    return otf;
  }


  var data = new Uint8Array(buff);
  // PATCHED: keep around the compressed data if we inflate it
  let compressedData;
  if (data[0] == 0x77) {
    compressedData = data;
    data = woffToOtf(data);
  }

  var tmap = {};
  var tag = bin.readASCII(data, 0, 4);
  if (tag == "ttcf") {
    var offset = 4;
    bin.readUshort(data, offset); offset += 2;
    bin.readUshort(data, offset); offset += 2;
    var numF = bin.readUint(data, offset); offset += 4;
    var fnts = [];
    for (var i = 0; i < numF; i++) {
      var foff = bin.readUint(data, offset); offset += 4;
      fnts.push(readFont(data, i, foff, tmap));
    }
    return fnts;
  }
  var fnt = readFont(data, 0, 0, tmap);  //console.log(fnt);  throw "e";
  fnt._compressedData = compressedData; // PATCH: make compressed data accessible
  var fvar = fnt["fvar"];
  if (fvar) {
    var out = [fnt];
    for (var i = 0; i < fvar[1].length; i++) {
      var fv = fvar[1][i];
      var obj = {}; out.push(obj); for (var p in fnt) obj[p] = fnt[p];
      obj["_index"] = i;
      var name = obj["name"] = JSON.parse(JSON.stringify(obj["name"]));
      name["fontSubfamily"] = fv[0];
      if (fv[3] == null) fv[3] = (name["fontFamily"] + "-" + name["fontSubfamily"])["replaceAll"](" ", "");
      name["postScriptName"] = fv[3];
    }
    return out;
  }

  return [fnt];
};


Typr["findTable"] = function (data, tab, foff) {
  var bin = Typr["B"];
  var numTables = bin.readUshort(data, foff + 4);
  var offset = foff + 12;
  for (var i = 0; i < numTables; i++) {
    var tag = bin.readASCII(data, offset, 4);   //console.log(tag);
    bin.readUint(data, offset + 4);
    var toffset = bin.readUint(data, offset + 8);
    var length = bin.readUint(data, offset + 12);
    if (tag == tab) return [toffset, length];
    offset += 16;
  }
  return null;
};
/*
Typr["splitBy"] = function(data,tag) {
  data = new Uint8Array(data);  console.log(data.slice(0,64));
  var bin = Typr["B"];
  var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};

  var offset = 8;
  var numF = bin.readUint  (data, offset);  offset+=4;
  var colls = [], used={};
  for(var i=0; i<numF; i++) {
    var foff = bin.readUint  (data, offset);  offset+=4;
    var toff = Typr["findTable"](data,tag,foff)[0];
    if(used[toff]==null) used[toff] = [];
    used[toff].push([foff,bin.readUshort(data,foff+4)]);  // font offset, numTables
  }
  for(var toff in used) {
    var offs = used[toff];
    var hlen = 12+4*offs.length;
    var out = new Uint8Array(hlen);
    for(var i=0; i<8; i++) out[i]=data[i];
    bin.writeUint(out,8,offs.length);

    for(var i=0; i<offs.length; i++) hlen += 12+offs[i][1]*16;

    var hdrs = [out], tabs = [], hoff=out.length, toff=hlen, noffs={};
    for(var i=0; i<offs.length; i++) {
      bin.writeUint(out, 12+i*4, hoff);  hoff+=12+offs[i][1]*16;
      toff = Typr["_cutFont"](data, offs[i][0], hdrs, tabs, toff, noffs);
    }
    colls.push(Typr["_joinArrs"](hdrs.concat(tabs)));
  }
  return colls;
}

Typr["splitFonts"] = function(data) {
  data = new Uint8Array(data);
  var bin = Typr["B"];
  var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};

  var offset = 8;
  var numF = bin.readUint  (data, offset);  offset+=4;
  var fnts = [];
  for(var i=0; i<numF; i++) {
    var foff = bin.readUint  (data, offset);  offset+=4;
    fnts.push(Typr._cutFont(data, foff));
    break;
  }
  return fnts;
}

Typr["_cutFont"] = function(data,foff,hdrs,tabs,toff, noffs) {
  var bin = Typr["B"];
  var numTables = bin.readUshort(data, foff+4);

  var out = new Uint8Array(12+numTables*16);  hdrs.push(out);
  for(var i=0; i<12; i++) out[i]=data[foff+i];  //console.log(out);

  var off = 12;
  for(var i=0; i<numTables; i++)
  {
    var tag      = bin.readASCII(data, foff+off, 4);
    var checkSum = bin.readUint (data, foff+off+ 4);
    var toffset  = bin.readUint (data, foff+off+ 8);
    var length   = bin.readUint (data, foff+off+12);

    while((length&3)!=0) length++;

    for(var j=0; j<16; j++) out[off+j]=data[foff+off+j];

    if(noffs[toffset]!=null) bin.writeUint(out,off+8,noffs[toffset]);
    else {
      noffs[toffset] = toff;
      bin.writeUint(out, off+8, toff);
      tabs.push(new Uint8Array(data.buffer, toffset, length));  toff+=length;
    }
    off+=16;
  }
  return toff;
}
Typr["_joinArrs"] = function(tabs) {
  var len = 0;
  for(var i=0; i<tabs.length; i++) len+=tabs[i].length;
  var out = new Uint8Array(len), ooff=0;
  for(var i=0; i<tabs.length; i++) {
    var tab = tabs[i];
    for(var j=0; j<tab.length; j++) out[ooff+j]=tab[j];
    ooff+=tab.length;
  }
  return out;
}
*/

Typr["T"] = {};





Typr["B"] = {
  readFixed: function (data, o) {
    return ((data[o] << 8) | data[o + 1]) + (((data[o + 2] << 8) | data[o + 3]) / (256 * 256 + 4));
  },
  readF2dot14: function (data, o) {
    var num = Typr["B"].readShort(data, o);
    return num / 16384;
  },
  readInt: function (buff, p) {
    //if(p>=buff.length) throw "error";
    var a = Typr["B"].t.uint8;
    a[0] = buff[p + 3];
    a[1] = buff[p + 2];
    a[2] = buff[p + 1];
    a[3] = buff[p];
    return Typr["B"].t.int32[0];
  },

  readInt8: function (buff, p) {
    //if(p>=buff.length) throw "error";
    var a = Typr["B"].t.uint8;
    a[0] = buff[p];
    return Typr["B"].t.int8[0];
  },
  readShort: function (buff, p) {
    //if(p>=buff.length) throw "error";
    var a = Typr["B"].t.uint16;
    a[0] = (buff[p] << 8) | buff[p + 1];
    return Typr["B"].t.int16[0];
  },
  readUshort: function (buff, p) {
    //if(p>=buff.length) throw "error";
    return (buff[p] << 8) | buff[p + 1];
  },
  writeUshort: function (buff, p, n) {
    buff[p] = (n >> 8) & 255; buff[p + 1] = n & 255;
  },
  readUshorts: function (buff, p, len) {
    var arr = [];
    for (var i = 0; i < len; i++) {
      var v = Typr["B"].readUshort(buff, p + i * 2);  //if(v==932) console.log(p+i*2);
      arr.push(v);
    }
    return arr;
  },
  readUint: function (buff, p) {
    //if(p>=buff.length) throw "error";
    var a = Typr["B"].t.uint8;
    a[3] = buff[p]; a[2] = buff[p + 1]; a[1] = buff[p + 2]; a[0] = buff[p + 3];
    return Typr["B"].t.uint32[0];
  },
  writeUint: function (buff, p, n) {
    buff[p] = (n >> 24) & 255; buff[p + 1] = (n >> 16) & 255; buff[p + 2] = (n >> 8) & 255; buff[p + 3] = (n >> 0) & 255;
  },
  readUint64: function (buff, p) {
    //if(p>=buff.length) throw "error";
    return (Typr["B"].readUint(buff, p) * (0xffffffff + 1)) + Typr["B"].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;
  },
  writeASCII: function (buff, p, s)	// l : length in Characters (not Bytes)
  {
    for (var i = 0; i < s.length; i++)
      buff[p + i] = s.charCodeAt(i);
  },
  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: globalThis["TextDecoder"] ? new globalThis["TextDecoder"]() : null,
  readUTF8: function (buff, p, l) {
    var tdec = Typr["B"]._tdec;
    if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff);
    return Typr["B"].readASCII(buff, p, l);
  },
  readBytes: function (buff, p, l) {
    //if(p>=buff.length) throw "error";
    var arr = [];
    for (var i = 0; i < l; i++) arr.push(buff[p + i]);
    return arr;
  },
  readASCIIArray: 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.push(String.fromCharCode(buff[p + i]));
    return s;
  },
  t: function () {
    var ab = new ArrayBuffer(8);
    return {
      buff: ab,
      int8: new Int8Array(ab),
      uint8: new Uint8Array(ab),
      int16: new Int16Array(ab),
      uint16: new Uint16Array(ab),
      int32: new Int32Array(ab),
      uint32: new Uint32Array(ab)
    }
  }()
};






Typr["T"].CFF = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var CFF = Typr["T"].CFF;

    data = new Uint8Array(data.buffer, offset, length);
    offset = 0;

    // Header
    data[offset]; offset++;
    data[offset]; offset++;
    data[offset]; offset++;
    data[offset]; offset++;
    //console.log(major, minor, hdrSize, offsize);

    // Name INDEX
    var ninds = [];
    offset = CFF.readIndex(data, offset, ninds);
    var names = [];

    for (var i = 0; i < ninds.length - 1; i++) names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i]));
    offset += ninds[ninds.length - 1];


    // Top DICT INDEX
    var tdinds = [];
    offset = CFF.readIndex(data, offset, tdinds);  //console.log(tdinds);
    // Top DICT Data
    var topDicts = [];
    for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1]));
    offset += tdinds[tdinds.length - 1];
    var topdict = topDicts[0];
    //console.log(topdict);

    // String INDEX
    var sinds = [];
    offset = CFF.readIndex(data, offset, sinds);
    // String Data
    var strings = [];
    for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i]));
    offset += sinds[sinds.length - 1];

    // Global Subr INDEX  (subroutines)
    CFF.readSubrs(data, offset, topdict);

    // charstrings

    if (topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]);

    // CID font
    if (topdict["ROS"]) {
      offset = topdict["FDArray"];
      var fdind = [];
      offset = CFF.readIndex(data, offset, fdind);

      topdict["FDArray"] = [];
      for (var i = 0; i < fdind.length - 1; i++) {
        var dict = CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]);
        CFF._readFDict(data, dict, strings);
        topdict["FDArray"].push(dict);
      }
      offset += fdind[fdind.length - 1];

      offset = topdict["FDSelect"];
      topdict["FDSelect"] = [];
      var fmt = data[offset]; offset++;
      if (fmt == 3) {
        var rns = bin.readUshort(data, offset); offset += 2;
        for (var i = 0; i < rns + 1; i++) {
          topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset + 2]); offset += 3;
        }
      }
      else throw fmt;
    }

    // Encoding
    //if(topdict["Encoding"]) topdict["Encoding"] = CFF.readEncoding(data, topdict["Encoding"], topdict["CharStrings"].length);

    // charset
    if (topdict["charset"]) topdict["charset"] = CFF.readCharset(data, topdict["charset"], topdict["CharStrings"].length);

    CFF._readFDict(data, topdict, strings);
    return topdict;
  },

  _readFDict: function (data, dict, ss) {
    var CFF = Typr["T"].CFF;
    var offset;
    if (dict["Private"]) {
      offset = dict["Private"][1];
      dict["Private"] = CFF.readDict(data, offset, offset + dict["Private"][0]);
      if (dict["Private"]["Subrs"]) CFF.readSubrs(data, offset + dict["Private"]["Subrs"], dict["Private"]);
    }
    for (var p in dict) if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35];
  },

  readSubrs: function (data, offset, obj) {
    obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset);

    var bias, nSubrs = obj["Subrs"].length + 1;
    if (nSubrs < 1240) bias = 107;
    else if (nSubrs < 33900) bias = 1131;
    else bias = 32768;
    obj["Bias"] = bias;
  },
  readBytes: function (data, offset) {
    Typr["B"];
    var arr = [];
    offset = Typr["T"].CFF.readIndex(data, offset, arr);

    var subrs = [], arl = arr.length - 1, no = data.byteOffset + offset;
    for (var i = 0; i < arl; i++) {
      var ari = arr[i];
      subrs.push(new Uint8Array(data.buffer, no + ari, arr[i + 1] - ari));
    }
    return subrs;
  },

  tableSE: [
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    1, 2, 3, 4, 5, 6, 7, 8,
    9, 10, 11, 12, 13, 14, 15, 16,
    17, 18, 19, 20, 21, 22, 23, 24,
    25, 26, 27, 28, 29, 30, 31, 32,
    33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48,
    49, 50, 51, 52, 53, 54, 55, 56,
    57, 58, 59, 60, 61, 62, 63, 64,
    65, 66, 67, 68, 69, 70, 71, 72,
    73, 74, 75, 76, 77, 78, 79, 80,
    81, 82, 83, 84, 85, 86, 87, 88,
    89, 90, 91, 92, 93, 94, 95, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 96, 97, 98, 99, 100, 101, 102,
    103, 104, 105, 106, 107, 108, 109, 110,
    0, 111, 112, 113, 114, 0, 115, 116,
    117, 118, 119, 120, 121, 122, 0, 123,
    0, 124, 125, 126, 127, 128, 129, 130,
    131, 0, 132, 133, 0, 134, 135, 136,
    137, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 138, 0, 139, 0, 0, 0, 0,
    140, 141, 142, 143, 0, 0, 0, 0,
    0, 144, 0, 0, 0, 145, 0, 0,
    146, 147, 148, 149, 0, 0, 0, 0
  ],

  glyphByUnicode: function (cff, code) {
    for (var i = 0; i < cff["charset"].length; i++) if (cff["charset"][i] == code) return i;
    return -1;
  },

  glyphBySE: function (cff, charcode)	// glyph by standard encoding
  {
    if (charcode < 0 || charcode > 255) return -1;
    return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]);
  },

  /*readEncoding : function(data, offset, num)
  {
    var bin = Typr["B"];

    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<nCodes; i++)  array.push(data[offset+i]);
    }
    /*
    else if(format==1 || format==2)
    {
      while(charset.length<num)
      {
        var first = bin.readUshort(data, offset);  offset+=2;
        var nLeft=0;
        if(format==1) {  nLeft = data[offset];  offset++;  }
        else          {  nLeft = bin.readUshort(data, offset);  offset+=2;  }
        for(var i=0; i<=nLeft; i++)  {  charset.push(first);  first++;  }
      }
    }

    else throw "error: unknown encoding format: " + format;

    return array;
  },*/

  readCharset: function (data, offset, num) {
    var bin = Typr["B"];

    var charset = ['.notdef'];
    var format = data[offset]; offset++;

    if (format == 0) {
      for (var i = 0; i < num; i++) {
        var first = bin.readUshort(data, offset); offset += 2;
        charset.push(first);
      }
    }
    else if (format == 1 || format == 2) {
      while (charset.length < num) {
        var first = bin.readUshort(data, offset); offset += 2;
        var nLeft = 0;
        if (format == 1) { nLeft = data[offset]; offset++; }
        else { nLeft = bin.readUshort(data, offset); offset += 2; }
        for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; }
      }
    }
    else throw "error: format: " + format;

    return charset;
  },

  readIndex: function (data, offset, inds) {
    var bin = Typr["B"];

    var count = bin.readUshort(data, offset) + 1; offset += 2;
    var offsize = data[offset]; offset++;

    if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]);
    else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2));
    else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 0x00ffffff);
    else if (offsize == 4) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 4));
    else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count;

    offset += count * offsize;
    return offset - 1;
  },

  getCharString: function (data, offset, o) {
    var bin = Typr["B"];

    var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4];
    var vs = 1;
    var op = null, val = null;
    // operand
    if (b0 <= 20) { op = b0; vs = 1; }
    if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
    //if(b0==19 || b0==20) { op = b0/*+" "+b1*/;  vs=2; }
    if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
    if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
    if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
    if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
    if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
    if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
    if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }

    o.val = val != null ? val : "o" + op;
    o.size = vs;
  },

  readCharString: function (data, offset, length) {
    var end = offset + length;
    var bin = Typr["B"];
    var arr = [];

    while (offset < end) {
      var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4];
      var vs = 1;
      var op = null, val = null;
      // operand
      if (b0 <= 20) { op = b0; vs = 1; }
      if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
      if (b0 == 19 || b0 == 20) { op = b0/*+" "+b1*/; vs = 2; }
      if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
      if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
      if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
      if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
      if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
      if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
      if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }

      arr.push(val != null ? val : "o" + op);
      offset += vs;

      //var cv = arr[arr.length-1];
      //if(cv==undefined) throw "error";
      //console.log()
    }
    return arr;
  },

  readDict: function (data, offset, end) {
    var bin = Typr["B"];
    //var dict = [];
    var dict = {};
    var carr = [];

    while (offset < end) {
      var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4];
      var vs = 1;
      var key = null, val = null;
      // operand
      if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
      if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; }
      if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
      if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
      if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
      if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; throw "unknown number"; }

      if (b0 == 30) {
        var nibs = [];
        vs = 1;
        while (true) {
          var b = data[offset + vs]; vs++;
          var nib0 = b >> 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 < nibs.length; i++) s += chars[nibs[i]];
        //console.log(nibs);
        val = parseFloat(s);
      }

      if (b0 <= 21)	// operator
      {
        var keys = ["version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues",
          "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs",
          "defaultWidthX", "nominalWidthX"];

        key = keys[b0]; vs = 1;
        if (b0 == 12) {
          var keys = ["Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale",
            "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "", "", "LanguageGroup", "ExpansionFactor", "initialRandomSeed",
            "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "", "", "", "", "", "",
            "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName"];
          key = keys[b1]; vs = 2;
        }
      }

      if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; }
      else carr.push(val);

      offset += vs;
    }
    return dict;
  }
};


Typr["T"].cmap = {
  parseTab: function (data, offset, length) {
    var obj = { tables: [], ids: {}, off: offset };
    data = new Uint8Array(data.buffer, offset, length);
    offset = 0;
    var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap;
    rU(data, offset); offset += 2;
    var numTables = rU(data, offset); offset += 2;

    //console.log(version, numTables);

    var offs = [];


    for (var i = 0; i < numTables; i++) {
      var platformID = rU(data, offset); offset += 2;
      var encodingID = rU(data, offset); offset += 2;
      var noffset = bin.readUint(data, offset); offset += 4;

      var id = "p" + platformID + "e" + encodingID;

      //console.log("cmap subtable", platformID, encodingID, noffset);


      var tind = offs.indexOf(noffset);

      if (tind == -1) {
        tind = obj.tables.length;
        var subt = {};
        offs.push(noffset);
        //var time = Date.now();
        var format = subt.format = rU(data, noffset);
        if (format == 0) subt = cmap.parse0(data, noffset, subt);
        //else if(format== 2) subt.off = noffset;
        else if (format == 4) subt = cmap.parse4(data, noffset, subt);
        else if (format == 6) subt = cmap.parse6(data, noffset, subt);
        else if (format == 12) subt = cmap.parse12(data, noffset, subt);
        //console.log(format, Date.now()-time);
        //else console.log("unknown format: "+format, platformID, encodingID, noffset);
        obj.tables.push(subt);
      }

      if (obj.ids[id] != null) console.log("multiple tables for one platform+encoding: " + id);
      obj.ids[id] = tind;
    }
    return obj;
  },

  parse0: function (data, offset, obj) {
    var bin = Typr["B"];
    offset += 2;
    var len = bin.readUshort(data, offset); offset += 2;
    bin.readUshort(data, offset); offset += 2;
    obj.map = [];
    for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]);
    return obj;
  },

  parse4: function (data, offset, obj) {
    var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts;
    var offset0 = offset;
    offset += 2;
    var length = rU(data, offset); offset += 2;
    rU(data, offset); offset += 2;
    var segCountX2 = rU(data, offset); offset += 2;
    var segCount = segCountX2 >>> 1;
    obj.searchRange = rU(data, offset); offset += 2;
    obj.entrySelector = rU(data, offset); offset += 2;
    obj.rangeShift = rU(data, offset); offset += 2;
    obj.endCount = rUs(data, offset, segCount); offset += segCount * 2;
    offset += 2;
    obj.startCount = rUs(data, offset, segCount); offset += segCount * 2;
    obj.idDelta = [];
    for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; }
    obj.idRangeOffset = rUs(data, offset, segCount); offset += segCount * 2;
    obj.glyphIdArray = rUs(data, offset, ((offset0 + length) - offset) >> 1);  //offset += segCount*2;
    return obj;
  },

  parse6: function (data, offset, obj) {
    var bin = Typr["B"];
    offset += 2;
    bin.readUshort(data, offset); offset += 2;
    bin.readUshort(data, offset); offset += 2;
    obj.firstCode = bin.readUshort(data, offset); offset += 2;
    var entryCount = bin.readUshort(data, offset); offset += 2;
    obj.glyphIdArray = [];
    for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; }

    return obj;
  },

  parse12: function (data, offset, obj) {
    var bin = Typr["B"], rU = bin.readUint;
    offset += 4;
    rU(data, offset); offset += 4;
    rU(data, offset); offset += 4;
    var nGroups = rU(data, offset) * 3; offset += 4;

    var gps = obj.groups = new Uint32Array(nGroups);//new Uint32Array(data.slice(offset, offset+nGroups*12).buffer);

    for (var i = 0; i < nGroups; i += 3) {
      gps[i] = rU(data, offset + (i << 2));
      gps[i + 1] = rU(data, offset + (i << 2) + 4);
      gps[i + 2] = rU(data, offset + (i << 2) + 8);
    }
    return obj;
  }
};

Typr["T"].CBLC = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"], ooff = offset;

    bin.readUshort(data, offset); offset += 2;
    bin.readUshort(data, offset); offset += 2;

    var numSizes = bin.readUint(data, offset); offset += 4;

    var out = [];
    for (var i = 0; i < numSizes; i++) {
      var off = bin.readUint(data, offset); offset += 4;  // indexSubTableArrayOffset
      bin.readUint(data, offset); offset += 4;  // indexTablesSize
      bin.readUint(data, offset); offset += 4;  // numberOfIndexSubTables
      offset += 4;

      offset += 2 * 12;

      bin.readUshort(data, offset); offset += 2;
      bin.readUshort(data, offset); offset += 2;

      //console.log(off,siz,num, sGlyph, eGlyph);

      offset += 4;

      var coff = ooff + off;
      for (var j = 0; j < 3; j++) {
        var fgI = bin.readUshort(data, coff); coff += 2;
        var lgI = bin.readUshort(data, coff); coff += 2;
        var nxt = bin.readUint(data, coff); coff += 4;
        var gcnt = lgI - fgI + 1;
        //console.log(fgI, lgI, nxt);   //if(nxt==0) break;

        var ioff = ooff + off + nxt;

        var inF = bin.readUshort(data, ioff); ioff += 2; if (inF != 1) throw inF;
        var imF = bin.readUshort(data, ioff); ioff += 2;
        var imgo = bin.readUint(data, ioff); ioff += 4;

        var oarr = [];
        for (var gi = 0; gi < gcnt; gi++) {
          var sbitO = bin.readUint(data, ioff + gi * 4); oarr.push(imgo + sbitO);
          //console.log("--",sbitO);
        }
        out.push([fgI, lgI, imF, oarr]);
      }
    }
    return out;
  }
};

Typr["T"].CBDT = {
  parseTab: function (data, offset, length) {
    Typr["B"];

    //var maj = bin.readUshort(data,offset);  offset+=2;
    //var min = bin.readUshort(data,offset);  offset+=2;

    return new Uint8Array(data.buffer, data.byteOffset + offset, length);
  }
};

Typr["T"].glyf = {
  parseTab: function (data, offset, length, font) {
    var obj = [], ng = font["maxp"]["numGlyphs"];
    for (var g = 0; g < ng; g++) obj.push(null);
    return obj;
  },

  _parseGlyf: function (font, g) {
    var bin = Typr["B"];
    var data = font["_data"], loca = font["loca"];

    if (loca[g] == loca[g + 1]) return null;

    var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g];

    var gl = {};

    gl.noc = bin.readShort(data, offset); offset += 2;		// number of contours
    gl.xMin = bin.readShort(data, offset); offset += 2;
    gl.yMin = bin.readShort(data, offset); offset += 2;
    gl.xMax = bin.readShort(data, offset); offset += 2;
    gl.yMax = bin.readShort(data, offset); offset += 2;

    if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null;

    if (gl.noc > 0) {
      gl.endPts = [];
      for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; }

      var instructionLength = bin.readUshort(data, offset); offset += 2;
      if ((data.length - offset) < instructionLength) return null;
      gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength;

      var crdnum = gl.endPts[gl.noc - 1] + 1;
      gl.flags = [];
      for (var i = 0; i < crdnum; i++) {
        var flag = data[offset]; offset++;
        gl.flags.push(flag);
        if ((flag & 8) != 0) {
          var rep = data[offset]; offset++;
          for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; }
        }
      }
      gl.xs = [];
      for (var i = 0; i < crdnum; i++) {
        var i8 = ((gl.flags[i] & 2) != 0), same = ((gl.flags[i] & 16) != 0);
        if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; }
        else {
          if (same) gl.xs.push(0);
          else { gl.xs.push(bin.readShort(data, offset)); offset += 2; }
        }
      }
      gl.ys = [];
      for (var i = 0; i < crdnum; i++) {
        var i8 = ((gl.flags[i] & 4) != 0), same = ((gl.flags[i] & 32) != 0);
        if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; }
        else {
          if (same) gl.ys.push(0);
          else { gl.ys.push(bin.readShort(data, offset)); offset += 2; }
        }
      }
      var x = 0, y = 0;
      for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; }
      //console.log(endPtsOfContours, instructionLength, instructions, flags, xCoordinates, yCoordinates);
    }
    else {
      var ARG_1_AND_2_ARE_WORDS = 1 << 0;
      var ARGS_ARE_XY_VALUES = 1 << 1;
      var WE_HAVE_A_SCALE = 1 << 3;
      var MORE_COMPONENTS = 1 << 5;
      var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
      var WE_HAVE_A_TWO_BY_TWO = 1 << 7;
      var WE_HAVE_INSTRUCTIONS = 1 << 8;

      gl.parts = [];
      var flags;
      do {
        flags = bin.readUshort(data, offset); offset += 2;
        var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part);
        part.glyphIndex = bin.readUshort(data, offset); offset += 2;
        if (flags & ARG_1_AND_2_ARE_WORDS) {
          var arg1 = bin.readShort(data, offset); offset += 2;
          var arg2 = bin.readShort(data, offset); offset += 2;
        } else {
          var arg1 = bin.readInt8(data, offset); offset++;
          var arg2 = bin.readInt8(data, offset); offset++;
        }

        if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; }
        else { part.p1 = arg1; part.p2 = arg2; }
        //part.m.tx = arg1;  part.m.ty = arg2;
        //else { throw "params are not XY values"; }

        if (flags & WE_HAVE_A_SCALE) {
          part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2;
        } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
          part.m.a = bin.readF2dot14(data, offset); offset += 2;
          part.m.d = bin.readF2dot14(data, offset); offset += 2;
        } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
          part.m.a = bin.readF2dot14(data, offset); offset += 2;
          part.m.b = bin.readF2dot14(data, offset); offset += 2;
          part.m.c = bin.readF2dot14(data, offset); offset += 2;
          part.m.d = bin.readF2dot14(data, offset); offset += 2;
        }
      } while (flags & MORE_COMPONENTS)
      if (flags & WE_HAVE_INSTRUCTIONS) {
        var numInstr = bin.readUshort(data, offset); offset += 2;
        gl.instr = [];
        for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; }
      }
    }
    return gl;
  }
};

Typr["T"].head = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var obj = {};
    bin.readFixed(data, offset); offset += 4;

    obj["fontRevision"] = bin.readFixed(data, offset); offset += 4;
    bin.readUint(data, offset); offset += 4;
    bin.readUint(data, offset); offset += 4;
    obj["flags"] = bin.readUshort(data, offset); offset += 2;
    obj["unitsPerEm"] = bin.readUshort(data, offset); offset += 2;
    obj["created"] = bin.readUint64(data, offset); offset += 8;
    obj["modified"] = bin.readUint64(data, offset); offset += 8;
    obj["xMin"] = bin.readShort(data, offset); offset += 2;
    obj["yMin"] = bin.readShort(data, offset); offset += 2;
    obj["xMax"] = bin.readShort(data, offset); offset += 2;
    obj["yMax"] = bin.readShort(data, offset); offset += 2;
    obj["macStyle"] = bin.readUshort(data, offset); offset += 2;
    obj["lowestRecPPEM"] = bin.readUshort(data, offset); offset += 2;
    obj["fontDirectionHint"] = bin.readShort(data, offset); offset += 2;
    obj["indexToLocFormat"] = bin.readShort(data, offset); offset += 2;
    obj["glyphDataFormat"] = bin.readShort(data, offset); offset += 2;
    return obj;
  }
};

Typr["T"].hhea = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var obj = {};
    bin.readFixed(data, offset); offset += 4;

    var keys = ["ascender", "descender", "lineGap",
      "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent",
      "caretSlopeRise", "caretSlopeRun", "caretOffset",
      "res0", "res1", "res2", "res3",
      "metricDataFormat", "numberOfHMetrics"];

    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var func = (key == "advanceWidthMax" || key == "numberOfHMetrics") ? bin.readUshort : bin.readShort;
      obj[key] = func(data, offset + i * 2);
    }
    return obj;
  }
};


Typr["T"].hmtx = {
  parseTab: function (data, offset, length, font) {
    var bin = Typr["B"];
    var aWidth = [];
    var lsBearing = [];

    var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"];
    var aw = 0, lsb = 0, i = 0;
    while (i < nH) { aw = bin.readUshort(data, offset + (i << 2)); lsb = bin.readShort(data, offset + (i << 2) + 2); aWidth.push(aw); lsBearing.push(lsb); i++; }
    while (i < nG) { aWidth.push(aw); lsBearing.push(lsb); i++; }

    return { aWidth: aWidth, lsBearing: lsBearing };
  }
};


Typr["T"].kern = {
  parseTab: function (data, offset, length, font) {
    var bin = Typr["B"], kern = Typr["T"].kern;

    var version = bin.readUshort(data, offset);
    if (version == 1) return kern.parseV1(data, offset, length, font);
    var nTables = bin.readUshort(data, offset + 2); offset += 4;

    var map = { glyph1: [], rval: [] };
    for (var i = 0; i < nTables; i++) {
      offset += 2;	// skip version
      var length = bin.readUshort(data, offset); offset += 2;
      var coverage = bin.readUshort(data, offset); offset += 2;
      var format = coverage >>> 8;
			/* I have seen format 128 once, that's why I do */ format &= 0xf;
      if (format == 0) offset = kern.readFormat0(data, offset, map);
      //else throw "unknown kern table format: "+format;
    }
    return map;
  },

  parseV1: function (data, offset, length, font) {
    var bin = Typr["B"], kern = Typr["T"].kern;

    bin.readFixed(data, offset);   // 0x00010000
    var nTables = bin.readUint(data, offset + 4); offset += 8;

    var map = { glyph1: [], rval: [] };
    for (var i = 0; i < nTables; i++) {
      bin.readUint(data, offset); offset += 4;
      var coverage = bin.readUshort(data, offset); offset += 2;
      bin.readUshort(data, offset); offset += 2;
      var format = coverage & 0xff;
      if (format == 0) offset = kern.readFormat0(data, offset, map);
      //else throw "unknown kern table format: "+format;
    }
    return map;
  },

  readFormat0: function (data, offset, map) {
    var bin = Typr["B"], rUs = bin.readUshort;
    var pleft = -1;
    var nPairs = rUs(data, offset);
    rUs(data, offset + 2);
    rUs(data, offset + 4);
    rUs(data, offset + 6); offset += 8;
    for (var j = 0; j < nPairs; j++) {
      var left = rUs(data, offset); offset += 2;
      var right = rUs(data, offset); offset += 2;
      var value = bin.readShort(data, offset); offset += 2;
      if (left != pleft) { map.glyph1.push(left); map.rval.push({ glyph2: [], vals: [] }); }
      var rval = map.rval[map.rval.length - 1];
      rval.glyph2.push(right); rval.vals.push(value);
      pleft = left;
    }
    return offset;
  }
};


Typr["T"].loca = {
  parseTab: function (data, offset, length, font) {
    var bin = Typr["B"];
    var obj = [];

    var ver = font["head"]["indexToLocFormat"];
    var len = font["maxp"]["numGlyphs"] + 1;

    if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1);
    if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2)));

    return obj;
  }
};


Typr["T"].maxp = {
  parseTab: function (data, offset, length) {
    //console.log(data.length, offset, length);

    var bin = Typr["B"], rU = bin.readUshort;
    var obj = {};

    // both versions 0.5 and 1.0
    bin.readUint(data, offset); offset += 4;

    obj["numGlyphs"] = rU(data, offset); offset += 2;

    // only 1.0
    /*
    if(ver == 0x00010000) {
      obj.maxPoints             = rU(data, offset);  offset += 2;
      obj.maxContours           = rU(data, offset);  offset += 2;
      obj.maxCompositePoints    = rU(data, offset);  offset += 2;
      obj.maxCompositeContours  = rU(data, offset);  offset += 2;
      obj.maxZones              = rU(data, offset);  offset += 2;
      obj.maxTwilightPoints     = rU(data, offset);  offset += 2;
      obj.maxStorage            = rU(data, offset);  offset += 2;
      obj.maxFunctionDefs       = rU(data, offset);  offset += 2;
      obj.maxInstructionDefs    = rU(data, offset);  offset += 2;
      obj.maxStackElements      = rU(data, offset);  offset += 2;
      obj.maxSizeOfInstructions = rU(data, offset);  offset += 2;
      obj.maxComponentElements  = rU(data, offset);  offset += 2;
      obj.maxComponentDepth     = rU(data, offset);  offset += 2;
    }
    */

    return obj;
  }
};
Typr["T"].name = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var obj = {};
    bin.readUshort(data, offset); offset += 2;
    var count = bin.readUshort(data, offset); offset += 2;
    var stringOffset = bin.readUshort(data, offset); offset += 2;

    var ooo = offset - 6 + stringOffset;
    //console.log(format,count);

    var names = [
      "copyright",
      "fontFamily",
      "fontSubfamily",
      "ID",
      "fullName",
      "version",
      "postScriptName",
      "trademark",
      "manufacturer",
      "designer",
      "description",
      "urlVendor",
      "urlDesigner",
      "licence",
      "licenceURL",
      "---",
      "typoFamilyName",
      "typoSubfamilyName",
      "compatibleFull",
      "sampleText",
      "postScriptCID",
      "wwsFamilyName",
      "wwsSubfamilyName",
      "lightPalette",
      "darkPalette"
    ];

    var rU = bin.readUshort;

    for (var i = 0; i < count; i++) {
      var platformID = rU(data, offset); offset += 2;
      var encodingID = rU(data, offset); offset += 2;
      var languageID = rU(data, offset); offset += 2;
      var nameID = rU(data, offset); offset += 2;
      var slen = rU(data, offset); offset += 2;
      var noffset = rU(data, offset); offset += 2;
      //console.log(platformID, encodingID, languageID.toString(16), nameID, length, noffset);


      var soff = ooo + noffset;
      var str;
      if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2);
      else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2);
      else if (platformID == 1 && encodingID == 25) str = bin.readUnicode(data, soff, slen / 2);
      else if (encodingID == 0) str = bin.readASCII(data, soff, slen);
      else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2);
      else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2);
      else if (encodingID == 4) str = bin.readUnicode(data, soff, slen / 2);
      else if (encodingID == 5) str = bin.readUnicode(data, soff, slen / 2);
      else if (encodingID == 10) str = bin.readUnicode(data, soff, slen / 2);

      else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.log("reading unknown MAC encoding " + encodingID + " as ASCII"); }
      else {
        console.log("unknown encoding " + encodingID + ", platformID: " + platformID);
        str = bin.readASCII(data, soff, slen);
      }

      var tid = "p" + platformID + "," + (languageID).toString(16);//Typr._platforms[platformID];
      if (obj[tid] == null) obj[tid] = {};
      var name = names[nameID]; if (name == null) name = "_" + nameID;
      obj[tid][name] = str;
      obj[tid]["_lang"] = languageID;
      //console.log(tid, obj[tid]);
    }
    /*
    if(format == 1)
    {
      var langTagCount = bin.readUshort(data, offset);  offset += 2;
      for(var i=0; i<langTagCount; i++)
      {
        var length  = bin.readUshort(data, offset);  offset += 2;
        var noffset = bin.readUshort(data, offset);  offset += 2;
      }
    }
    */
    var out = Typr["T"].name.selectOne(obj), ff = "fontFamily";
    if (out[ff] == null) for (var p in obj) if (obj[p][ff] != null) out[ff] = obj[p][ff];
    return out;
  },
  selectOne: function (obj) {
    //console.log(obj);
    var psn = "postScriptName";

    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0409) return obj[p];		// United States
    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0000) return obj[p];		// Universal
    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0c0c) return obj[p];		// Canada
    for (var p in obj) if (obj[p][psn] != null) return obj[p];

    var out;
    for (var p in obj) { out = obj[p]; break; }
    console.log("returning name table with languageID " + out._lang);
    if (out[psn] == null && out["ID"] != null) out[psn] = out["ID"];
    return out;
  }
};

Typr["T"].OS2 = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var ver = bin.readUshort(data, offset); offset += 2;

    var OS2 = Typr["T"].OS2;

    var obj = {};
    if (ver == 0) OS2.version0(data, offset, obj);
    else if (ver == 1) OS2.version1(data, offset, obj);
    else if (ver == 2 || ver == 3 || ver == 4) OS2.version2(data, offset, obj);
    else if (ver == 5) OS2.version5(data, offset, obj);
    else throw "unknown OS/2 table version: " + ver;

    return obj;
  },

  version0: function (data, offset, obj) {
    var bin = Typr["B"];
    obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2;
    obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2;
    obj["usWidthClass"] = bin.readUshort(data, offset); offset += 2;
    obj["fsType"] = bin.readUshort(data, offset); offset += 2;
    obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2;
    obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2;
    obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2;
    obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2;
    obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2;
    obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2;
    obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2;
    obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2;
    obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2;
    obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2;
    obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2;
    obj["panose"] = bin.readBytes(data, offset, 10); offset += 10;
    obj["ulUnicodeRange1"] = bin.readUint(data, offset); offset += 4;
    obj["ulUnicodeRange2"] = bin.readUint(data, offset); offset += 4;
    obj["ulUnicodeRange3"] = bin.readUint(data, offset); offset += 4;
    obj["ulUnicodeRange4"] = bin.readUint(data, offset); offset += 4;
    obj["achVendID"] = bin.readASCII(data, offset, 4); offset += 4;
    obj["fsSelection"] = bin.readUshort(data, offset); offset += 2;
    obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2;
    obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2;
    obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2;
    obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2;
    obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2;
    obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2;
    obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2;
    return offset;
  },

  version1: function (data, offset, obj) {
    var bin = Typr["B"];
    offset = Typr["T"].OS2.version0(data, offset, obj);

    obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4;
    obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4;
    return offset;
  },

  version2: function (data, offset, obj) {
    var bin = Typr["B"], rU = bin.readUshort;
    offset = Typr["T"].OS2.version1(data, offset, obj);

    obj["sxHeight"] = bin.readShort(data, offset); offset += 2;
    obj["sCapHeight"] = bin.readShort(data, offset); offset += 2;
    obj["usDefault"] = rU(data, offset); offset += 2;
    obj["usBreak"] = rU(data, offset); offset += 2;
    obj["usMaxContext"] = rU(data, offset); offset += 2;
    return offset;
  },

  version5: function (data, offset, obj) {
    var rU = Typr["B"].readUshort;
    offset = Typr["T"].OS2.version2(data, offset, obj);

    obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2;
    obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2;
    return offset;
  }
};

Typr["T"].post = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var obj = {};

    obj["version"] = bin.readFixed(data, offset); offset += 4;
    obj["italicAngle"] = bin.readFixed(data, offset); offset += 4;
    obj["underlinePosition"] = bin.readShort(data, offset); offset += 2;
    obj["underlineThickness"] = bin.readShort(data, offset); offset += 2;

    return obj;
  }
};
Typr["T"].SVG = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var obj = { entries: [], svgs: [] };

    var offset0 = offset;

    bin.readUshort(data, offset); offset += 2;
    var svgDocIndexOffset = bin.readUint(data, offset); offset += 4;
    bin.readUint(data, offset); offset += 4;

    offset = svgDocIndexOffset + offset0;

    var numEntries = bin.readUshort(data, offset); offset += 2;

    for (var i = 0; i < numEntries; i++) {
      var startGlyphID = bin.readUshort(data, offset); offset += 2;
      var endGlyphID = bin.readUshort(data, offset); offset += 2;
      var svgDocOffset = bin.readUint(data, offset); offset += 4;
      var svgDocLength = bin.readUint(data, offset); offset += 4;

      var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength);
      if (sbuf[0] == 0x1f && sbuf[1] == 0x8b && sbuf[2] == 0x08) sbuf = pako["inflate"](sbuf);
      var svg = bin.readUTF8(sbuf, 0, sbuf.length);

      for (var f = startGlyphID; f <= endGlyphID; f++) {
        obj.entries[f] = obj.svgs.length;
      }
      obj.svgs.push(svg);
    }
    return obj;
  }
};


Typr["T"].sbix = {
  parseTab: function (data, offset, length, obj) {
    var numGlyphs = obj["maxp"]["numGlyphs"];
    var ooff = offset;
    var bin = Typr["B"];

    //var ver = bin.readUshort(data,offset);  offset+=2;
    //var flg = bin.readUshort(data,offset);  offset+=2;

    var numStrikes = bin.readUint(data, offset + 4);

    var out = [];
    for (var si = numStrikes - 1; si < numStrikes; si++) {
      var off = ooff + bin.readUint(data, offset + 8 + si * 4);

      //var ppem = bin.readUshort(data,off);  off+=2;
      //var ppi  = bin.readUshort(data,off);  off+=2;

      for (var gi = 0; gi < numGlyphs; gi++) {
        var aoff = bin.readUint(data, off + 4 + gi * 4);
        var noff = bin.readUint(data, off + 4 + gi * 4 + 4); if (aoff == noff) { out[gi] = null; continue; }
        var go = off + aoff;
        //var ooX = bin.readUshort(data,go);
        //var ooY = bin.readUshort(data,go+2);
        var tag = bin.readASCII(data, go + 4, 4); if (tag != "png ") throw tag;

        out[gi] = new Uint8Array(data.buffer, data.byteOffset + go + 8, noff - aoff - 8);
      }
    }
    return out;
  }
};

Typr["T"].colr = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var ooff = offset;
    offset += 2;
    var num = bin.readUshort(data, offset); offset += 2;

    var boff = bin.readUint(data, offset); offset += 4;
    var loff = bin.readUint(data, offset); offset += 4;

    var lnum = bin.readUshort(data, offset); offset += 2;
    //console.log(num,boff,loff,lnum);

    var base = {};
    var coff = ooff + boff;
    for (var i = 0; i < num; i++) {
      base["g" + bin.readUshort(data, coff)] = [bin.readUshort(data, coff + 2), bin.readUshort(data, coff + 4)];
      coff += 6;
    }

    var lays = [];
    coff = ooff + loff;
    for (var i = 0; i < lnum; i++) {
      lays.push(bin.readUshort(data, coff), bin.readUshort(data, coff + 2)); coff += 4;
    }
    return [base, lays];
  }
};

Typr["T"].cpal = {
  parseTab: function (data, offset, length) {
    var bin = Typr["B"];
    var ooff = offset;
    var vsn = bin.readUshort(data, offset); offset += 2;

    if (vsn == 0) {
      bin.readUshort(data, offset); offset += 2;
      bin.readUshort(data, offset); offset += 2;
      var tot = bin.readUshort(data, offset); offset += 2;

      var fst = bin.readUint(data, offset); offset += 4;

      return new Uint8Array(data.buffer, ooff + fst, tot * 4);
      /*
      var coff=ooff+fst;

      for(var i=0; i<tot; i++) {
        console.log(data[coff],data[coff+1],data[coff+2],data[coff+3]);
        coff+=4;
      }

      console.log(ets,pts,tot); */
    }
    else throw vsn;//console.log("unknown color palette",vsn);
  }
};

Typr["T"].GSUB = {
  parseTab: function (data, offset, length, obj) {
    //console.log(obj.name.ID);

    var bin = Typr["B"], rU = bin.readUshort; bin.readUint;


    var off = offset;
    rU(data, off); off += 2;
    rU(data, off); off += 2;
    rU(data, off); off += 2;
    var flO = rU(data, off); off += 2;
    rU(data, off); off += 2;

    //console.log(maj,min,slO,flO,llO);

    off = offset + flO;

    var fmap = {};
    var cnt = rU(data, off); off += 2;
    for (var i = 0; i < cnt; i++) {
      var tag = bin.readASCII(data, off, 4); off += 4;
      rU(data, off); off += 2;
      fmap[tag] = true;
    }
    //console.log(fmap);
    return fmap;
  }
};

Typr["T"].fvar = {
  parseTab: function (data, offset, length, obj) {
    var name = obj["name"];
    var off = offset;
    var bin = Typr["B"];
    var axes = [], inst = [];

    off += 8;
    var acnt = bin.readUshort(data, off); off += 2;
    off += 2;
    var icnt = bin.readUshort(data, off); off += 2;
    var isiz = bin.readUshort(data, off); off += 2;

    for (var i = 0; i < acnt; i++) {
      var tag = bin.readASCII(data, off, 4);
      var min = bin.readFixed(data, off + 4);
      var def = bin.readFixed(data, off + 8);
      var max = bin.readFixed(data, off + 12);
      var flg = bin.readUshort(data, off + 16);
      var nid = bin.readUshort(data, off + 18);
      axes.push([tag, min, def, max, flg, name["_" + nid]]);
      //console.log(tag,min,def,max,flg,nid);
      off += 20;
    }
    for (var i = 0; i < icnt; i++) {
      var snid = bin.readUshort(data, off), pnid = null;
      var flg = bin.readUshort(data, off + 2);
      var crd = []; for (var j = 0; j < acnt; j++) crd.push(bin.readFixed(data, off + 4 + j * 4));
      off += 4 + acnt * 4;
      if ((isiz & 3) == 2) { pnid = bin.readUshort(data, off); off += 2; }
      inst.push([name["_" + snid], flg, crd, pnid]);
      //console.log(snid,flg, crd);
    }

    return [axes, inst];
  }
};

Typr["T"].gvar = (function () {

  var EMBEDDED_PEAK_TUPLE = 0x8000;
  var INTERMEDIATE_REGION = 0x4000;
  var PRIVATE_POINT_NUMBERS = 0x2000;

  var DELTAS_ARE_ZERO = 0x80;
  var DELTAS_ARE_WORDS = 0x40;

  var POINTS_ARE_WORDS = 0x80;

  var SHARED_POINT_NUMBERS = 0x8000;

  var bin = Typr["B"];

  function readTuple(data, o, acnt) {
    var tup = []; for (var j = 0; j < acnt; j++) tup.push(bin.readF2dot14(data, o + j * 2));
    return tup;
  }

  function readTupleVarHeader(data, off, vcnt, acnt, eoff) {
    var out = [];
    for (var j = 0; j < vcnt; j++) {
      var dsiz = bin.readUshort(data, off); off += 2;
      var tind = bin.readUshort(data, off), flag = tind & 0xf000; tind = tind & 0xfff; off += 2;
      //console.log(j, dsiz,tind, flag.toString(16));

      var peak = null, start = null, end = null;
      if (flag & EMBEDDED_PEAK_TUPLE) { peak = readTuple(data, off, acnt); off += acnt * 2; }
      if (flag & INTERMEDIATE_REGION) { start = readTuple(data, off, acnt); off += acnt * 2; }
      if (flag & INTERMEDIATE_REGION) { end = readTuple(data, off, acnt); off += acnt * 2; }
      out.push([dsiz, tind, flag, start, peak, end]);
    }
    return out;
  }

  // Packed "point" numbers
  function readPointNumbers(data, off, gid) {
    var cnt = data[off]; off++; if (cnt == 0) return [[], off];
    if (127 < cnt) { cnt = ((cnt & 127) << 8) | data[off++]; }

    //if(gid==116) console.log("---",cnt);
    var pts = [], last = 0;  // point number data runs
    while (pts.length < cnt) {
      var v = data[off]; off++;
      var wds = (v & POINTS_ARE_WORDS) != 0; v = (v & 127) + 1;
      //if(gid==116) console.log("-",v);
      for (var i = 0; i < v; i++) {
        var dif = 0;
        if (wds) { dif = bin.readUshort(data, off); off += 2; }
        else { dif = data[off]; off++; }
        //if(gid==116) console.log(dif);
        last += dif;
        pts.push(last);
      }
    }
    //console.log(pts);
    return [pts, off];


    //throw "e";
  }


  function parseTab(data, offset, length, obj) {
    var off = offset + 4;
    var acnt = bin.readUshort(data, off); off += 2;
    var tcnt = bin.readUshort(data, off); off += 2;
    var toff = bin.readUint(data, off); off += 4;
    var gcnt = bin.readUshort(data, off); off += 2;
    bin.readUshort(data, off); off += 2;

    var goff = bin.readUint(data, off); off += 4;

    // glyphVariationDataOffsets
    var offs = []; for (var i = 0; i < gcnt + 1; i++) offs.push(bin.readUint(data, off + i * 4));


    // sharedTuples
    var tups = [], mins = [], maxs = []; off = offset + toff;
    for (var i = 0; i < tcnt; i++) {
      var peak = readTuple(data, off + i * acnt * 2, acnt), imin = [], imax = []; tups.push(peak); mins.push(imin); maxs.push(imax);
      for (var k = 0; k < acnt; k++) {
        imin[k] = Math.min(peak[k], 0);
        imax[k] = Math.max(peak[k], 0);
      }
    }
    //console.log(tups);

    //console.log(acnt,stcnt,stoff,gcnt,flgs,goff);

    var i8 = new Int8Array(data.buffer);

    // GlyphVariationData table array
    var tabs = [];
    for (var i = 0; i < gcnt; i++) {
      //console.log("-------",i);
      off = offset + goff + offs[i];
      // tupleVariationCount
      var vcnt = bin.readUshort(data, off); off += 2;  //if((vcnt>>>12)!=0) throw "e";

      var snum = vcnt & SHARED_POINT_NUMBERS; vcnt &= 0xfff;
      //  offset to the serialized data
      var soff = bin.readUshort(data, off); off += 2;

      var hdr = readTupleVarHeader(data, off, vcnt, acnt);

      var tab = []; tabs.push(tab);
      // Serialized Data
      off = offset + goff + offs[i] + soff;

      var sind = null;
      if (snum) {
        var oo = readPointNumbers(data, off);
        sind = oo[0]; off = oo[1];
      }

      for (var j = 0; j < vcnt; j++) {
        var vr = hdr[j], end = off + vr[0];  //console.log(vr);  console.log(data.slice(off,off+vr[0]));

        var ind = sind;
        if (vr[2] & PRIVATE_POINT_NUMBERS) {
          var oo = readPointNumbers(data, off);
          ind = oo[0]; off = oo[1];
        }
        // read packed deltas (delta runs)
        var ds = [];
        while (off < end) {
          var cb = data[off++];  // control byte;
          var cnt = (cb & 0x3f) + 1;
          if (cb & DELTAS_ARE_ZERO) { for (var k = 0; k < cnt; k++) ds.push(0); }
          else if (cb & DELTAS_ARE_WORDS) { for (var k = 0; k < cnt; k++) ds.push(bin.readShort(data, off + k * 2)); off += cnt * 2; }
          else { for (var k = 0; k < cnt; k++) ds.push(i8[off + k]); off += cnt; }
        }
        //if(ind) console.log(ind, ds);
        var ti = vr[1];

        tab.push([[
          vr[3] ? vr[3] : mins[ti],
          vr[4] ? vr[4] : tups[ti],
          vr[5] ? vr[5] : maxs[ti]
        ], ds, ind.length == 0 ? null : ind]);

        if (ind.length != 0 && ind.length * 2 != ds.length) throw "e";
        //if(i==116) console.log(ind, ds);
      }
    }
    return tabs;
  }

  return { parseTab: parseTab };
})();

Typr["T"].avar = {
  parseTab: function (data, offset, length, obj) {
    var off = offset;
    var bin = Typr["B"], out = [];

    off += 6;
    var acnt = bin.readUshort(data, off); off += 2;

    for (var ai = 0; ai < acnt; ai++) {
      var cnt = bin.readUshort(data, off); off += 2;
      var poly = []; out.push(poly);
      for (var i = 0; i < cnt; i++) {
        var x = bin.readF2dot14(data, off);
        var y = bin.readF2dot14(data, off + 2); off += 4;
        poly.push(x, y);
      }
    }

    return out;
  }
};

Typr["T"].HVAR = {
  parseTab: function (data, offset, length, obj) {
    var off = offset, oo = offset;
    var bin = Typr["B"];

    //console.log(data.slice(off));
    off += 4;

    var varO = bin.readUint(data, off); off += 4;
    var advO = bin.readUint(data, off); off += 4;
    var lsbO = bin.readUint(data, off); off += 4;
    var rsbO = bin.readUint(data, off); off += 4;
    if (lsbO != 0 || rsbO != 0) throw lsbO;

    //console.log(varO,advO,lsbO,rsbO);

    off = oo + varO;  // item variation store

    // ItemVariationStore
    var ioff = off;

    var fmt = bin.readUshort(data, off); off += 2; if (fmt != 1) throw "e";
    var vregO = bin.readUint(data, off); off += 4;
    // itemVariationDataCount
    var vcnt = bin.readUshort(data, off); off += 2;

    var offs = []; for (var i = 0; i < vcnt; i++) offs.push(bin.readUint(data, off + i * 4)); off += vcnt * 4;  //if(offs.length!=1) throw "e";
    //console.log(vregO,vcnt,offs);

    off = ioff + vregO;
    var acnt = bin.readUshort(data, off); off += 2;
    var rcnt = bin.readUshort(data, off); off += 2;

    var regs = [];
    for (var i = 0; i < rcnt; i++) {
      var crd = [[], [], []]; regs.push(crd);
      for (var j = 0; j < acnt; j++) {
        crd[0].push(bin.readF2dot14(data, off + 0));
        crd[1].push(bin.readF2dot14(data, off + 2));
        crd[2].push(bin.readF2dot14(data, off + 4));
        off += 6;
      }
    }
    //console.log(acnt, rcnt, regs);


    var i8 = new Int8Array(data.buffer);
    var varStore = [];
    for (var i = 0; i < offs.length; i++) {
      // ItemVariationData
      off = oo + varO + offs[i]; var vdata = []; varStore.push(vdata);
      var icnt = bin.readUshort(data, off); off += 2;  // itemCount
      var dcnt = bin.readUshort(data, off); off += 2; if (dcnt & 0x8000) throw "e";
      var rcnt = bin.readUshort(data, off); off += 2;
      var ixs = []; for (var j = 0; j < rcnt; j++) ixs.push(bin.readUshort(data, off + j * 2)); off += rcnt * 2;
      //console.log(icnt,dcnt,rcnt,ixs);
      //console.log(data.slice(off));

      for (var k = 0; k < icnt; k++) {  // deltaSets
        var deltaData = [];  //vdata.push(deltaData);
        for (var ri = 0; ri < rcnt; ri++) {
          deltaData.push(ri < dcnt ? bin.readShort(data, off) : i8[off]);
          off += ri < dcnt ? 2 : 1;

        }
        var dd = new Array(regs.length); dd.fill(0); vdata.push(dd);
        for (var j = 0; j < ixs.length; j++) dd[ixs[j]] = deltaData[j];
      }
    }

    //console.log(varStore);

    // VariationRegionList



    off = oo + advO;  // advance widths

    // DeltaSetIndexMap

    var fmt = data[off++]; if (fmt != 0) throw "e";
    var entryFormat = data[off++];

    var mapCount = bin.readUshort(data, off); off += 2;

    var INNER_INDEX_BIT_COUNT_MASK = 0x0f;
    var MAP_ENTRY_SIZE_MASK = 0x30;
    var entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1;  //if(entrySize!=1) throw entrySize;

    //console.log(fmt, entryFormat, mapCount, entrySize);

    var dfs = [];
    for (var i = 0; i < mapCount; i++) {
      var entry = 0;
      if (entrySize == 1) entry = data[off++];
      else { entry = bin.readUshort(data, off); off += 2; }
      var outerIndex = entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1);
      var innerIndex = entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1);
      //map.push(outerIndex,innerIndex);
      dfs.push(varStore[outerIndex][innerIndex]);
      //console.log(outerIndex,innerIndex);
      //console.log(i,varStore[outerIndex][innerIndex]);
    }

    return [regs, dfs];
  }
};

Typr["U"] = function () {
  var P = {
    MoveTo: function (p, x, y) { p.cmds.push("M"); p.crds.push(x, y); },
    LineTo: function (p, x, y) { p.cmds.push("L"); p.crds.push(x, y); },
    CurveTo: function (p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); },
    qCurveTo: function (p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); },
    ClosePath: function (p) { p.cmds.push("Z"); }
  };

  function getGlyphPosition(font, gls, i1, ltr) {
    var g1 = gls[i1], g2 = gls[i1 + 1], kern = font["kern"];
    if (kern) {
      var ind1 = kern.glyph1.indexOf(g1);
      if (ind1 != -1) {
        var ind2 = kern.rval[ind1].glyph2.indexOf(g2);
        if (ind2 != -1) return [0, 0, kern.rval[ind1].vals[ind2], 0];
      }
    }
    //console.log("no kern");
    return [0, 0, 0, 0];
  }
  function shape(font, str, prm) {
    if (prm == null) prm = {};
    prm["ltr"]; prm["fts"]; var axs = prm["axs"];
    if (font["fvar"] && axs == null) axs = font["fvar"][1][font["_index"]][2];

    var HVAR = font["HVAR"];  //console.log(HVAR);
    if (axs && HVAR) { axs = _normalizeAxis(font, axs); }  //console.log(S,axs);
    var gls = [];
    for (var i = 0; i < str.length; i++) {
      var cc = str.codePointAt(i); if (cc > 0xffff) i++;
      gls.push(codeToGlyph(font, cc));
    }
    var shape = [];

    for (var i = 0; i < gls.length; i++) {
      var padj = getGlyphPosition(font, gls, i);
      var gid = gls[i];  //console.log(gid);
      var ax = font["hmtx"].aWidth[gid] + padj[2];
      if (HVAR && HVAR[1][gid]) { //ax+=S*HVAR[1][gid][0];
        var difs = HVAR[1][gid];  //console.log(difs);
        for (var j = 0; j < HVAR[0].length; j++) {
          ax += _interpolate(HVAR[0][j], axs) * difs[j];
        }
      }
      shape.push({ "g": gid, "cl": i, "dx": 0, "dy": 0, "ax": ax, "ay": 0 });
    }
    return shape;
  }

  function shapeToPath(font, shape, prm) {
    var tpath = { cmds: [], crds: [] };
    var x = 0, y = 0, clr, axs;
    if (prm) { clr = prm["clr"]; axs = prm["axs"]; }

    for (var i = 0; i < shape.length; i++) {
      var it = shape[i];
      var path = glyphToPath(font, it["g"], false, axs), crds = path["crds"];
      for (var j = 0; j < crds.length; j += 2) {
        tpath.crds.push(crds[j] + x + it["dx"]);
        tpath.crds.push(crds[j + 1] + y + it["dy"]);
      }
      if (clr) tpath.cmds.push(clr);
      for (var j = 0; j < path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]);
      var clen = tpath.cmds.length;
      if (clr) if (clen != 0 && tpath.cmds[clen - 1] != "X") tpath.cmds.push("X");  // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs.

      x += it["ax"]; y += it["ay"];
    }
    return { "cmds": tpath.cmds, "crds": tpath.crds };
  }


  // find the greatest index with a value <=v
  function arrSearch(arr, k, v) {
    var l = 0, r = ~~(arr.length / k);
    while (l + 1 != r) { var mid = l + ((r - l) >>> 1); if (arr[mid * k] <= v) l = mid; else r = mid; }

    //var mi = 0;  for(var i=0; i<arr.length; i+=k) if(arr[i]<=v) mi=i;  if(mi!=l*k) throw "e";

    return l * k;
  }

  var wha = [0x9, 0xa, 0xb, 0xc, 0xd, 0x20, 0x85, 0xa0, 0x1680, 0x180e, 0x2028, 0x2029, 0x202f, 0x2060, 0x3000, 0xfeff], whm = {};
  for (var i = 0; i < wha.length; i++) whm[wha[i]] = 1;
  for (var i = 0x2000; i <= 0x200d; i++) whm[i] = 1;

  function codeToGlyph(font, code) {
    //console.log(cmap);
    // "p3e10" for NotoEmoji-Regular.ttf
    //console.log(cmap);

    if (font["_ctab"] == null) {
      var cmap = font["cmap"];
      var tind = -1, pps = ["p3e10", "p0e4", "p3e1", "p1e0", "p0e3", "p0e1"/*,"p3e3"*/, "p3e0" /*Hebrew*/, "p3e5" /*Korean*/];
      for (var i = 0; i < pps.length; i++) if (cmap.ids[pps[i]] != null) { tind = cmap.ids[pps[i]]; break; }
      if (tind == -1) throw "no familiar platform and encoding!";
      font["_ctab"] = cmap.tables[tind];
    }

    var tab = font["_ctab"], fmt = tab.format, gid = -1;  //console.log(fmt); throw "e";

    if (fmt == 0) {
      if (code >= tab.map.length) gid = 0;
      else gid = tab.map[code];
    }
    /*else if(fmt==2) {
      var data=font["_data"], off = cmap.off+tab.off+6, bin=Typr["B"];
      var shKey = bin.readUshort(data,off + 2*(code>>>8));
      var shInd = off + 256*2 + shKey*8;

      var firstCode = bin.readUshort(data,shInd);
      var entryCount= bin.readUshort(data,shInd+2);
      var idDelta   = bin.readShort (data,shInd+4);
      var idRangeOffset = bin.readUshort(data,shInd+6);

      if(firstCode<=code && code<=firstCode+entryCount) {
        // not completely correct
        gid = bin.readUshort(data, shInd+6+idRangeOffset + (code&255)*2);
      }
      else gid=0;
      //if(code>256) console.log(code,(code>>>8),shKey,firstCode,entryCount,idDelta,idRangeOffset);

      //throw "e";
      //console.log(tab,  bin.readUshort(data,off));
      //throw "e";
    }*/
    else if (fmt == 4) {
      var ec = tab.endCount; gid = 0;
      if (code <= ec[ec.length - 1]) {
        // smallest index with code <= value
        var sind = arrSearch(ec, 1, code);
        if (ec[sind] < code) sind++;

        if (code >= tab.startCount[sind]) {
          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];
          gid = (gli & 0xFFFF);
        }
      }
    }
    else if (fmt == 6) {
      var off = code - tab.firstCode, arr = tab.glyphIdArray;
      if (off < 0 || off >= arr.length) gid = 0;
      else gid = arr[off];
    }
    else if (fmt == 12) {
      var grp = tab.groups; gid = 0;  //console.log(grp);  throw "e";

      if (code <= grp[grp.length - 2]) {
        var i = arrSearch(grp, 3, code);
        if (grp[i] <= code && code <= grp[i + 1]) { gid = grp[i + 2] + (code - grp[i]); }
      }
    }
    else throw "unknown cmap table format " + tab.format;

    //*
    var SVG = font["SVG "], loca = font["loca"];
    // if the font claims to have a Glyph for a character, but the glyph is empty, and the character is not "white", it is a lie!
    if (gid != 0 && font["CFF "] == null && (SVG == null || SVG.entries[gid] == null) && loca && loca[gid] == loca[gid + 1]  // loca not present in CFF or SVG fonts
      && whm[code] == null) gid = 0;
    //*/

    return gid;
  }
  function glyphToPath(font, gid, noColor, axs) {
    var path = { cmds: [], crds: [] };

    if (font["fvar"]) {
      if (axs == null) axs = font["fvar"][1][font["_index"]][2];
      axs = _normalizeAxis(font, axs);
    }

    var SVG = font["SVG "], CFF = font["CFF "], COLR = font["COLR"], CBLC = font["CBLC"], CBDT = font["CBDT"], sbix = font["sbix"], upng = window["UPNG"];

    var strike = null;
    if (CBLC && upng) for (var i = 0; i < CBLC.length; i++) if (CBLC[i][0] <= gid && gid <= CBLC[i][1]) strike = CBLC[i];

    if (strike || (sbix && sbix[gid])) {
      if (strike && strike[2] != 17) throw "not a PNG";

      if (font["__tmp"] == null) font["__tmp"] = {};
      var cmd = font["__tmp"]["g" + gid];
      if (cmd == null) {
        var bmp, len;
        if (sbix) { bmp = sbix[gid]; len = bmp.length; }
        else {
          var boff = strike[3][gid - strike[0]] + 5;  // smallGlyphMetrics
          len = (CBDT[boff + 1] << 16) | (CBDT[boff + 2] << 8) | CBDT[boff + 3]; boff += 4;
          bmp = new Uint8Array(CBDT.buffer, CBDT.byteOffset + boff, len);
        }
        var str = ""; for (var i = 0; i < len; i++) str += String.fromCharCode(bmp[i]);
        cmd = font["__tmp"]["g" + gid] = "data:image/png;base64," + btoa(str);
      }

      path.cmds.push(cmd);
      var upe = font["head"]["unitsPerEm"] * 1.15;
      var gw = Math.round(upe), gh = Math.round(upe), dy = Math.round(-gh * 0.15);
      path.crds.push(0, gh + dy, gw, gh + dy, gw, dy, 0, dy); //*/
    }
    else if (SVG && SVG.entries[gid]) {
      var p = SVG.entries[gid];
      if (p != null) {
        if (typeof p == "number") {
          var svg = SVG.svgs[p];
          if (typeof svg == "string") {
            var prsr = new DOMParser();
            var doc = prsr["parseFromString"](svg, "image/svg+xml");
            svg = SVG.svgs[p] = doc.getElementsByTagName("svg")[0];
          }
          p = Typr["U"]["SVG"].toPath(svg, gid); SVG.entries[gid] = p;
        }
        path = p;
      }
    }
    else if (noColor != true && COLR && COLR[0]["g" + gid] && COLR[0]["g" + gid][1] > 1) {

      function toHex(n) { var o = n.toString(16); return (o.length == 1 ? "0" : "") + o; }

      var CPAL = font["CPAL"], gl = COLR[0]["g" + gid];
      for (var i = 0; i < gl[1]; i++) {
        var lid = gl[0] + i;
        var cgl = COLR[1][2 * lid], pid = COLR[1][2 * lid + 1] * 4;
        var pth = glyphToPath(font, cgl, cgl == gid);

        var col = "#" + toHex(CPAL[pid + 2]) + toHex(CPAL[pid + 1]) + toHex(CPAL[pid + 0]);
        path.cmds.push(col);

        path.cmds = path.cmds.concat(pth["cmds"]);
        path.crds = path.crds.concat(pth["crds"]);
        //console.log(gid, cgl,pid,col);

        path.cmds.push("X");
      }
    }
    else if (CFF) {
      var pdct = CFF["Private"];
      var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: pdct ? pdct["defaultWidthX"] : 0, open: false };
      if (CFF["ROS"]) {
        var gi = 0;
        while (CFF["FDSelect"][gi + 2] <= gid) gi += 2;
        pdct = CFF["FDArray"][CFF["FDSelect"][gi + 1]]["Private"];
      }
      _drawCFF(CFF["CharStrings"][gid], state, CFF, pdct, path);
    }
    else if (font["glyf"]) { _drawGlyf(gid, font, path, axs); }
    return { "cmds": path.cmds, "crds": path.crds };
  }

  function _drawGlyf(gid, font, path, axs) {
    var gl = font["glyf"][gid];

    if (gl == null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid);
    if (gl != null) {
      if (gl.noc > -1) _simpleGlyph(gl, font, gid, path, axs);
      else _compoGlyph(gl, font, gid, path, axs);
    }
  }
  function _interpolate(axs, v) {
    v.length; var S = 1;
    var s = axs[0];  // start
    var p = axs[1];  // peak
    var e = axs[2];  // end

    for (var i = 0; i < v.length; i++) {
      var AS = 1;
      if (s[i] > p[i] || p[i] > e[i]) AS = 1;
      else if (s[i] < 0 && e[i] > 0 && p[i] != 0) AS = 1;
      else if (p[i] == 0) AS = 1;
      else if (v[i] < s[i] || v[i] > e[i]) AS = 0;
      else {
        if (v[i] == p[i]) AS = 1;
        else if (v[i] < p[i]) AS = (v[i] - s[i]) / (p[i] - s[i]);
        else AS = (e[i] - v[i]) / (e[i] - p[i]);
      }
      S = S * AS;
    }
    return S;
  }
  function _normalizeAxis(font, vv) {
    var fvar = font["fvar"], avar = font["avar"];
    var fv = fvar ? fvar[0] : null;

    var nv = [];
    for (var i = 0; i < fv.length; i++) {
      var min = fv[i][1], def = fv[i][2], max = fv[i][3], v = Math.max(min, Math.min(max, vv[i]));
      if (v < def) nv[i] = (def - v) / (min - def);
      else if (v > def) nv[i] = (v - def) / (max - def);
      else nv[i] = 0;

      if (avar && nv[i] != -1) {
        var av = avar[i], j = 0;
        for (; j < av.length; j += 2) if (av[j] >= nv[i]) break;
        var f = (nv[i] - av[j - 2]) / (av[j] - av[j - 2]);
        nv[i] = f * av[j + 1] + (1 - f) * av[j - 1];
      }

    }
    return nv;
  }
  function interpolateDeltas(dfs, ind, xs, ys, endPts) {
    var N = xs.length, ndfs = new Array(N * 2 + 8); ndfs.fill(0);
    for (var i = 0; i < N; i++) {
      var dx = 0, dy = 0, ii = ind.indexOf(i);
      if (ii != -1) { dx = dfs[ii]; dy = dfs[ind.length + ii]; }
      else {
        var cmp = 0; while (endPts[cmp] < i) cmp++;
        var cmp0 = cmp == 0 ? 0 : endPts[cmp - 1] + 1;
        var cmp1 = endPts[cmp];

        var i0 = -1, i1 = -1;

        for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; i0 = j; if (i1 == -1) i1 = j; }
        for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; if (v < i) i0 = j; if (i < v) { i1 = j; break; } }

        //var i0 = ind.length-1, i1=0;  if(ind[i0]>=N) i0--;
        //for(var j=0; j<ind.length; j++) {  var v=ind[j];  if(v<N) { if(v<i) i0=j;  if(i<v) {  i1=j;  break;  }  }  }
        for (var ax = 0; ax < 2; ax++) {
          var crd = ax == 0 ? xs : ys, ofs = ax * ind.length, dlt = 0;
          var c0 = crd[ind[i0]], c1 = crd[ind[i1]], cC = crd[i];
          var d0 = dfs[ofs + i0], d1 = dfs[ofs + i1];

          if (c0 == c1) {
            if (d0 == d1) dlt = d0;
            else dlt = 0;
          }
          else {
            if (cC <= Math.min(c0, c1)) {
              if (c0 < c1) dlt = d0;
              else dlt = d1;
            }
            else if (Math.max(c0, c1) <= cC) {
              if (c0 < c1) dlt = d1;
              else dlt = d0;
            }
            else {
              var prop = (cC - c0) / (c1 - c0);  //if(prop<0) throw "e";
              dlt = prop * d1 + (1 - prop) * d0;
            }
          }
          if (ax == 0) dx = dlt; else dy = dlt;
        }
      }
      ndfs[i] = dx; ndfs[N + 4 + i] = dy;
    }
    return ndfs;
  }
  function _simpleGlyph(gl, font, gid, p, axs) {
    var xs = gl.xs, ys = gl.ys;
    //*
    if (font["fvar"] && axs) {
      xs = xs.slice(0); ys = ys.slice(0);
      var gvar = font["gvar"];
      var gv = gvar ? gvar[gid] : null;

      for (var vi = 0; vi < gv.length; vi++) {
        var axv = gv[vi][0];  //console.log(axs);
        var S = _interpolate(axv, axs); if (S < 1e-9) continue;
        var dfs = gv[vi][1], ind = gv[vi][2];  //if(dfs.length!=2*xs.length+8) throw "e";
        //console.log(vi,S,axv,ind,dfs);
        if (ind) { dfs = gv[vi][1] = interpolateDeltas(dfs, ind, xs, ys, gl.endPts); gv[vi][2] = null; }
        //if(ind==null)
        if (dfs.length == xs.length * 2 + 8)
          for (var i = 0; i < xs.length; i++) {
            xs[i] += S * dfs[i];
            ys[i] += S * dfs[i + xs.length + 4];
          }
      }
    } //*/

    for (var c = 0; c < gl.noc; c++) {
      var i0 = (c == 0) ? 0 : (gl.endPts[c - 1] + 1);
      var il = gl.endPts[c];

      for (var i = i0; i <= il; i++) {
        var pr = (i == i0) ? il : (i - 1);
        var nx = (i == il) ? i0 : (i + 1);
        var onCurve = gl.flags[i] & 1;
        var prOnCurve = gl.flags[pr] & 1;
        var nxOnCurve = gl.flags[nx] & 1;

        var x = xs[i], y = ys[i];

        if (i == i0) {
          if (onCurve) {
            if (prOnCurve) P.MoveTo(p, xs[pr], ys[pr]);
            else { P.MoveTo(p, x, y); continue;  /*  will do CurveTo at il  */ }
          }
          else {
            if (prOnCurve) P.MoveTo(p, xs[pr], ys[pr]);
            else P.MoveTo(p, Math.floor((xs[pr] + x) * 0.5), Math.floor((ys[pr] + y) * 0.5));
          }
        }
        if (onCurve) {
          if (prOnCurve) P.LineTo(p, x, y);
        }
        else {
          if (nxOnCurve) P.qCurveTo(p, x, y, xs[nx], ys[nx]);
          else P.qCurveTo(p, x, y, Math.floor((x + xs[nx]) * 0.5), Math.floor((y + ys[nx]) * 0.5));
        }
      }
      P.ClosePath(p);
    }
  }
  function _compoGlyph(gl, font, gid, p, axs) {

    var dx = [0, 0, 0, 0, 0, 0], dy = [0, 0, 0, 0, 0, 0], ccnt = gl.parts.length;

    if (font["fvar"] && axs) {
      var gvar = font["gvar"];
      var gv = gvar ? gvar[gid] : null;
      for (var vi = 0; vi < gv.length; vi++) {
        var axv = gv[vi][0];  //console.log(axs);
        var S = _interpolate(axv, axs); if (S < 1e-6) continue;
        var dfs = gv[vi][1], ind = gv[vi][2];  //if(dfs.length!=2*ccnt+8) throw "e";
        if (ind == null)
          for (var i = 0; i < ccnt; i++) {
            dx[i] += S * dfs[i];
            dy[i] += S * dfs[i + ccnt + 4];
          }
        else
          for (var j = 0; j < ind.length; j++) {
            var i = ind[j];
            dx[i] += S * dfs[0];
            dy[i] += S * dfs[0 + ccnt];
          }
      }
    }

    for (var j = 0; j < ccnt; j++) {
      var path = { cmds: [], crds: [] };
      var prt = gl.parts[j];
      _drawGlyf(prt.glyphIndex, font, path, axs);

      var m = prt.m, tx = m.tx + dx[j], ty = m.ty + dy[j];
      for (var i = 0; i < path.crds.length; i += 2) {
        var x = path.crds[i], y = path.crds[i + 1];
        p.crds.push(x * m.a + y * m.c + tx);   // not sure, probably right
        p.crds.push(x * m.b + y * m.d + ty);
      }
      for (var i = 0; i < path.cmds.length; i++) p.cmds.push(path.cmds[i]);
    }
  }

  function pathToSVG(path, prec) {
    var cmds = path["cmds"], crds = path["crds"];
    if (prec == null) prec = 5;
    function num(v) { return parseFloat(v.toFixed(prec)); }
    function merge(o) {
      var no = [], lstF = false, lstC = "";
      for (var i = 0; i < o.length; i++) {
        var it = o[i], isF = (typeof it) == "number";
        if (!isF) { if (it == lstC && it.length == 1 && it != "m") continue; lstC = it; }  // move should not be merged (it actually means lineTo)
        if (lstF && isF && it >= 0) no.push(" ");
        no.push(it); lstF = isF;
      }
      return no.join("");
    }


    var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 };
    var x = 0, y = 0, // perfect coords
      //dx=0, dy=0, // relative perfect coords
      //rx=0, ry=0, // relative rounded coords
      ex = 0, ey = 0, // error between perfect and output coords
      mx = 0, my = 0; // perfect coords of the last "Move"

    for (var i = 0; i < cmds.length; i++) {
      var cmd = cmds[i], cc = (lmap[cmd] ? lmap[cmd] : 0);

      var o0 = [], dx, dy, rx, ry;  // o1=[], cx, cy, ax,ay;
      if (cmd == "L") {
        dx = crds[co] - x; dy = crds[co + 1] - y;
        rx = num(dx + ex); ry = num(dy + ey);
        // if this "lineTo" leads to the starting point, and "Z" follows, do not output anything.
        if (cmds[i + 1] == "Z" && crds[co] == mx && crds[co + 1] == my) { rx = dx; ry = dy; }
        else if (rx == 0 && ry == 0) ;
        else if (rx == 0) o0.push("v", ry);
        else if (ry == 0) o0.push("h", rx);
        else { o0.push("l", rx, ry); }
      }
      else {
        o0.push(cmd.toLowerCase());
        for (var j = 0; j < cc; j += 2) {
          dx = crds[co + j] - x; dy = crds[co + j + 1] - y;
          rx = num(dx + ex); ry = num(dy + ey);
          o0.push(rx, ry);
        }
      }
      if (cc != 0) { ex += dx - rx; ey += dy - ry; }

      var ou = o0;
      for (var j = 0; j < ou.length; j++) out.push(ou[j]);

      if (cc != 0) { co += cc; x = crds[co - 2]; y = crds[co - 1]; }
      if (cmd == "M") { mx = x; my = y; }
      if (cmd == "Z") { x = mx; y = my; }
    }

    return merge(out);
  }
  function SVGToPath(d) {
    var pth = { cmds: [], crds: [] };
    Typr["U"]["SVG"].svgToPath(d, pth);
    return { "cmds": pth.cmds, "crds": pth.crds };
  }

  function mipmapB(buff, w, h, hlp) {
    var nw = w >> 1, nh = h >> 1;
    var nbuf = new Uint8Array(nw * nh * 4);
    var sb32 = new Uint32Array(buff.buffer), nb32 = new Uint32Array(nbuf.buffer);
    for (var y = 0; y < nh; y++)
      for (var x = 0; x < nw; x++) {
        var ti = (y * nw + x), si = ((y << 1) * w + (x << 1));
        //nbuf[ti  ] = buff[si  ];  nbuf[ti+1] = buff[si+1];  nbuf[ti+2] = buff[si+2];  nbuf[ti+3] = buff[si+3];
        //*
        var c0 = sb32[si], c1 = sb32[si + 1], c2 = sb32[si + w], c3 = sb32[si + w + 1];

        var a0 = (c0 >>> 24), a1 = (c1 >>> 24), a2 = (c2 >>> 24), a3 = (c3 >>> 24), a = (a0 + a1 + a2 + a3);

        if (a == 1020) {
          var r = (((c0 >>> 0) & 255) + ((c1 >>> 0) & 255) + ((c2 >>> 0) & 255) + ((c3 >>> 0) & 255) + 2) >>> 2;
          var g = (((c0 >>> 8) & 255) + ((c1 >>> 8) & 255) + ((c2 >>> 8) & 255) + ((c3 >>> 8) & 255) + 2) >>> 2;
          var b = (((c0 >>> 16) & 255) + ((c1 >>> 16) & 255) + ((c2 >>> 16) & 255) + ((c3 >>> 16) & 255) + 2) >>> 2;
          nb32[ti] = (255 << 24) | (b << 16) | (g << 8) | r;
        }
        else if (a == 0) nb32[ti] = 0;
        else {
          var r = ((c0 >>> 0) & 255) * a0 + ((c1 >>> 0) & 255) * a1 + ((c2 >>> 0) & 255) * a2 + ((c3 >>> 0) & 255) * a3;
          var g = ((c0 >>> 8) & 255) * a0 + ((c1 >>> 8) & 255) * a1 + ((c2 >>> 8) & 255) * a2 + ((c3 >>> 8) & 255) * a3;
          var b = ((c0 >>> 16) & 255) * a0 + ((c1 >>> 16) & 255) * a1 + ((c2 >>> 16) & 255) * a2 + ((c3 >>> 16) & 255) * a3;

          var ia = 1 / a; r = ~~(r * ia + 0.5); g = ~~(g * ia + 0.5); b = ~~(b * ia + 0.5);
          nb32[ti] = (((a + 2) >>> 2) << 24) | (b << 16) | (g << 8) | r;
        }
      }
    return { buff: nbuf, w: nw, h: nh };
  }

  var __cnv, __ct;
  function pathToContext(path, ctx) {
    var c = 0, cmds = path["cmds"], crds = path["crds"];

    //ctx.translate(3500,500);  ctx.rotate(0.25);  ctx.scale(1,-1);

    for (var j = 0; j < cmds.length; j++) {
      var cmd = cmds[j];
      if (cmd == "M") {
        ctx.moveTo(crds[c], crds[c + 1]);
        c += 2;
      }
      else if (cmd == "L") {
        ctx.lineTo(crds[c], crds[c + 1]);
        c += 2;
      }
      else if (cmd == "C") {
        ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]);
        c += 6;
      }
      else if (cmd == "Q") {
        ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]);
        c += 4;
      }
      else if (cmd[0] == "d") {
        var upng = window["UPNG"];
        var x0 = crds[c], y0 = crds[c + 1], x1 = crds[c + 2], y1 = crds[c + 3], x2 = crds[c + 4], y2 = crds[c + 5], x3 = crds[c + 6], y3 = crds[c + 7]; c += 8;
        //y0+=400;  y1+=400;  y1+=600;
        if (upng == null) {
          ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.closePath();
          continue;
        }
        var dx0 = (x1 - x0), dy0 = (y1 - y0), dx1 = (x3 - x0), dy1 = (y3 - y0);
        var sbmp = atob(cmd.slice(22));
        var bmp = new Uint8Array(sbmp.length);
        for (var i = 0; i < sbmp.length; i++) bmp[i] = sbmp.charCodeAt(i);

        var img = upng["decode"](bmp.buffer), w = img["width"], h = img["height"];  //console.log(img);

        var nbmp = new Uint8Array(upng["toRGBA8"](img)[0]);
        var tr = ctx["getTransform"]();
        var scl = Math.sqrt(Math.abs(tr["a"] * tr["d"] - tr["b"] * tr["c"])) * Math.sqrt(dx1 * dx1 + dy1 * dy1) / h;
        while (scl < 0.5) {
          var nd = mipmapB(nbmp, w, h);
          nbmp = nd.buff; w = nd.w; h = nd.h; scl *= 2;
        }

        if (__cnv == null) { __cnv = document.createElement("canvas"); __ct = __cnv.getContext("2d"); }
        if (__cnv.width != w || __cnv.height != h) { __cnv.width = w; __cnv.height = h; }

        __ct.putImageData(new ImageData(new Uint8ClampedArray(nbmp.buffer), w, h), 0, 0);
        ctx.save();
        ctx.transform(dx0, dy0, dx1, dy1, x0, y0);
        ctx.scale(1 / w, 1 / h);
        ctx.drawImage(__cnv, 0, 0); //*/
        ctx.restore();
      }
      else if (cmd.charAt(0) == "#" || cmd.charAt(0) == "r") {
        ctx.beginPath();
        ctx.fillStyle = cmd;
      }
      else if (cmd.charAt(0) == "O" && cmd != "OX") {
        ctx.beginPath();
        var pts = cmd.split("-");
        ctx.lineWidth = parseFloat(pts[2]);
        ctx.lineCap = ["butt", "round", "square"][parseFloat(pts[3])];
        ctx.lineJoin = ["miter", "round", "bevel"][parseFloat(pts[4])];
        ctx.miterLimit = parseFloat(pts[5]);
        ctx.lineDashOffset = parseFloat(pts[6]);
        ctx.setLineDash(pts[7].split(",").map(parseFloat));
        ctx.strokeStyle = pts[1];
      }
      else if (cmd == "Z") {
        ctx.closePath();
      }
      else if (cmd == "X") {
        ctx.fill();
      }
      else if (cmd == "OX") {
        ctx.stroke();
      }
    }
  }


  function _drawCFF(cmds, state, font, pdct, p) {
    var stack = state.stack;
    var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open;
    var i = 0;
    var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0;
    var CFF = Typr["T"].CFF;

    var nominalWidthX = pdct["nominalWidthX"];
    var o = { val: 0, size: 0 };
    //console.log(cmds);
    while (i < cmds.length) {
      CFF.getCharString(cmds, i, o);
      var v = o.val;
      i += o.size;

      if (v == "o1" || v == "o18")  //  hstem || hstemhm
      {
        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() + nominalWidthX;
        }

        nStems += stack.length >> 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() + nominalWidthX;
        }

        nStems += stack.length >> 1;
        stack.length = 0;
        haveWidth = true;
      }
      else if (v == "o4") {
        if (stack.length > 1 && !haveWidth) {
          width = stack.shift() + nominalWidthX;
          haveWidth = true;
        }
        if (open) P.ClosePath(p);

        y += stack.pop();
        P.MoveTo(p, x, y); open = true;
      }
      else if (v == "o5") {
        while (stack.length > 0) {
          x += stack.shift();
          y += stack.shift();
          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 < count; j++) {
          var sval = stack.shift();

          if (isX) x += sval; else y += sval;
          isX = !isX;
          P.LineTo(p, x, y);
        }
      }
      else if (v == "o8" || v == "o24")	// rrcurveto || rcurveline
      {
        var count = stack.length;
        var index = 0;
        while (index + 6 <= count) {
          c1x = x + stack.shift();
          c1y = y + stack.shift();
          c2x = c1x + stack.shift();
          c2y = c1y + stack.shift();
          x = c2x + stack.shift();
          y = c2y + stack.shift();
          P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
          index += 6;
        }
        if (v == "o24") {
          x += stack.shift();
          y += stack.shift();
          P.LineTo(p, x, y);
        }
      }
      else if (v == "o11") break;
      else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237")//if((v+"").slice(0,3)=="o12")
      {
        if (v == "o1234") {
          c1x = x + stack.shift();    // dx1
          c1y = y;                      // dy1
          c2x = c1x + stack.shift();    // dx2
          c2y = c1y + stack.shift();    // dy2
          jpx = c2x + stack.shift();    // dx3
          jpy = c2y;                    // dy3
          c3x = jpx + stack.shift();    // dx4
          c3y = c2y;                    // dy4
          c4x = c3x + stack.shift();    // dx5
          c4y = y;                      // dy5
          x = c4x + stack.shift();      // dx6
          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);

        }
        if (v == "o1235") {
          c1x = x + stack.shift();    // dx1
          c1y = y + stack.shift();    // dy1
          c2x = c1x + stack.shift();    // dx2
          c2y = c1y + stack.shift();    // dy2
          jpx = c2x + stack.shift();    // dx3
          jpy = c2y + stack.shift();    // dy3
          c3x = jpx + stack.shift();    // dx4
          c3y = jpy + stack.shift();    // dy4
          c4x = c3x + stack.shift();    // dx5
          c4y = c3y + stack.shift();    // dy5
          x = c4x + stack.shift();      // dx6
          y = c4y + stack.shift();      // dy6
          stack.shift();                // flex depth
          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        }
        if (v == "o1236") {
          c1x = x + stack.shift();    // dx1
          c1y = y + stack.shift();    // dy1
          c2x = c1x + stack.shift();    // dx2
          c2y = c1y + stack.shift();    // dy2
          jpx = c2x + stack.shift();    // dx3
          jpy = c2y;                    // dy3
          c3x = jpx + stack.shift();    // dx4
          c3y = c2y;                    // dy4
          c4x = c3x + stack.shift();    // dx5
          c4y = c3y + stack.shift();    // dy5
          x = c4x + stack.shift();      // dx6
          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        }
        if (v == "o1237") {
          c1x = x + stack.shift();    // dx1
          c1y = y + stack.shift();    // dy1
          c2x = c1x + stack.shift();    // dx2
          c2y = c1y + stack.shift();    // dy2
          jpx = c2x + stack.shift();    // dx3
          jpy = c2y + stack.shift();    // dy3
          c3x = jpx + stack.shift();    // dx4
          c3y = jpy + stack.shift();    // dy4
          c4x = c3x + stack.shift();    // dx5
          c4y = c3y + stack.shift();    // dy5
          if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
            x = c4x + stack.shift();
          } else {
            y = c4y + stack.shift();
          }
          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        }
      }
      else if (v == "o14") {
        if (stack.length > 0 && stack.length != 4 && !haveWidth) {
          width = stack.shift() + font["nominalWidthX"];
          haveWidth = true;
        }
        if (stack.length == 4) // seac = standard encoding accented character
        {
          var adx = stack.shift();
          var ady = stack.shift();
          var bchar = stack.shift();
          var achar = stack.shift();


          var bind = CFF.glyphBySE(font, bchar);
          var aind = 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;

          _drawCFF(font["CharStrings"][bind], state, font, pdct, p);
          state.x = adx; state.y = ady;
          _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) { 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() + 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() + nominalWidthX;
          haveWidth = true;
        }

        y += stack.pop();
        x += stack.pop();

        if (open) P.ClosePath(p);
        P.MoveTo(p, x, y); open = true;
      }
      else if (v == "o22") {
        if (stack.length > 1 && !haveWidth) {
          width = stack.shift() + nominalWidthX;
          haveWidth = true;
        }

        x += stack.pop();

        if (open) P.ClosePath(p);
        P.MoveTo(p, x, y); open = true;
      }
      else if (v == "o25") {
        while (stack.length > 6) {
          x += stack.shift();
          y += stack.shift();
          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();
        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();
          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;
          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;
          _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 & -3;
        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;
          }
          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;
  }

  function initHB(hurl, resp) {
    var codeLength = function (code) {
      var len = 0;
      if ((code & (0xffffffff - (1 << 7) + 1)) == 0) { len = 1; }
      else if ((code & (0xffffffff - (1 << 11) + 1)) == 0) { len = 2; }
      else if ((code & (0xffffffff - (1 << 16) + 1)) == 0) { len = 3; }
      else if ((code & (0xffffffff - (1 << 21) + 1)) == 0) { len = 4; }
      return len;
    };

    fetch(hurl)
      .then(function (x) { return x["arrayBuffer"](); })
      .then(function (ab) { return WebAssembly["instantiate"](ab); })
      .then(function (res) {
        console.log("HB ready");
        var exp = res["instance"]["exports"], mem = exp["memory"];
        //mem["grow"](30); // each page is 64kb in size
        var heapu8, u32, i32, f32;
        var __lastFnt, blob, blobPtr, face, font;

        Typr["U"]["shapeHB"] = (function () {

          var toJson = function (ptr) {
            var length = exp["hb_buffer_get_length"](ptr);
            var result = [];
            var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>> 2;
            var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>> 2;
            for (var i = 0; i < length; ++i) {
              var a = iPtr32 + i * 5, b = pPtr32 + i * 5;
              result.push({
                "g": u32[a + 0],
                "cl": u32[a + 2],
                "ax": i32[b + 0],
                "ay": i32[b + 1],
                "dx": i32[b + 2],
                "dy": i32[b + 3]
              });
            }
            //console.log(result);
            return result;
          };
          var te;

          return function (fnt, str, prm) {
            var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"];
            var ltr = prm["ltr"], fts = prm["fts"], axs = prm["axs"];
            if (fnt["fvar"] && axs == null) axs = fnt["fvar"][1][fnt["_index"]][2];

            //var olen = mem.buffer.byteLength, nlen = 2*fdata.length+str.length*16 + 4e6;
            //if(olen<nlen) mem["grow"](((nlen-olen)>>>16)+4);  //console.log("growing",nlen);

            heapu8 = new Uint8Array(mem.buffer);
            u32 = new Uint32Array(mem.buffer);
            i32 = new Int32Array(mem.buffer);
            f32 = new Float32Array(mem.buffer);

            if (__lastFnt != fn) {
              if (blob != null) {
                exp["hb_blob_destroy"](blob);
                exp["free"](blobPtr);
                exp["hb_face_destroy"](face);
                exp["hb_font_destroy"](font);
              }
              blobPtr = exp["malloc"](fdata.byteLength); heapu8.set(fdata, blobPtr);
              blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0);
              face = exp["hb_face_create"](blob, fnt["_index"]);
              font = exp["hb_font_create"](face);
              __lastFnt = fn;
            }
            if (window["TextEncoder"] == null) { alert("Your browser is too old. Please, update it."); return; }
            if (te == null) te = new window["TextEncoder"]("utf8");

            var buffer = exp["hb_buffer_create"]();
            var bytes = te["encode"](str);
            var len = bytes.length, strp = exp["malloc"](len); heapu8.set(bytes, strp);
            exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len);
            exp["free"](strp);

            var bin = Typr["B"];

            var feat = 0;
            if (fts) {
              feat = exp["malloc"](16 * fts.length);
              for (var i = 0; i < fts.length; i++) {
                var fe = fts[i];
                var off = feat + i * 16, qo = off >>> 2;
                bin.writeASCII(heapu8, off, fe[0].split("").reverse().join(""));
                u32[qo + 1] = fe[1];
                u32[qo + 2] = fe[2];
                u32[qo + 3] = fe[3];
              }
              //console.log(fts);
            }
            var vdat = 0;
            if (axs && fnt["fvar"]) {
              var axes = fnt["fvar"][0];  //console.log(axes, axs);
              vdat = exp["malloc"](8 * axs.length);
              for (var i = 0; i < axs.length; i++) {
                var off = vdat + i * 8, qo = off >>> 2;
                bin.writeASCII(heapu8, off, axes[i][0].split("").reverse().join(""));
                f32[qo + 1] = axs[i];
              }
            }
            //*/

            if (axs) exp["hb_font_set_variations"](font, vdat, axs.length);
            exp["hb_buffer_set_direction"](buffer, ltr ? 4 : 5);
            exp["hb_buffer_guess_segment_properties"](buffer);
            exp["hb_shape"](font, buffer, feat, fts ? fts.length : 0);
            var json = toJson(buffer);//buffer["json"]();
            exp["hb_buffer_destroy"](buffer);
            if (fts) exp["free"](feat);
            if (axs) exp["free"](vdat);

            var arr = json.slice(0); if (!ltr) arr.reverse();
            var ci = 0, bi = 0;  // character index, binary index
            for (var i = 1; i < arr.length; i++) {
              var gl = arr[i], cl = gl["cl"];
              while (true) {
                var cpt = str.codePointAt(ci), cln = codeLength(cpt);
                if (bi + cln <= cl) { bi += cln; ci += cpt <= 0xffff ? 1 : 2; }
                else break;
              }
              //while(bi+codeLength(str.charCodeAt(ci)) <=cl) {  bi+=codeLength(str.charCodeAt(ci));  ci++;  }
              gl["cl"] = ci;
            }
            return json;
          }
        }());
        resp();
      });
  }

  return { "shape": shape, "shapeToPath": shapeToPath, "codeToGlyph": codeToGlyph, "glyphToPath": glyphToPath, "pathToSVG": pathToSVG, "SVGToPath": SVGToPath, "pathToContext": pathToContext, "initHB": initHB };
}();

export { Typr as default };
